Delegate Error in Swift - ios

I am trying to send a double value from a UIView (which is loaded from a XIB) to a ViewController using a delegate
Here is my protocol, it is just sending a double to the main ViewController on a button click.
protocol HoursWorkedDelegate{
func sendHoursWorked(hoursWorked: Double);
}
class HoursWorkedView: UIView{
var delegate: HoursWorkedDelegate?;
#IBAction func calculateHoursWorked(sender: AnyObject){
// Do some calculations for the hoursWorked value
// Give value
delegate!.sendHoursWorked(hoursWorked);
}
}
// This class now wants that Double value
class ViewController: UIViewController, HoursWorkedDelegate{
// Conform to protocol
func sendHoursWorked(hoursWorked: Double){
// Lets say we just want to assign this value to a textField
hoursWorkedTextField.text = NSString(format: "%.4f", hoursWorked);
}
}
The error message I get is Thread 1: EXC_BAD_INSTRUCTION(code = EXC_I386_INVOP, subcode=0x0)
Any help would be much appreciated, Thank You!

As a start, change the exclamation point in this snippet to a question mark:
delegate!.sendHoursWorked(hoursWorked);
This is what's likely causing the crash, as you are force-unwrapping the optional delegate property. A question mark means we'll only call sendHoursWorked() on the delegate if the delegate exists.
That fix will now probably mean that your program is no longer crashing, but you still don't get the desired results, because sendHoursWorked() is never called. We have to tell our HoursWorkedView object who is delegating it.
Somewhere in your code, you might have something like this:
let hoursWorkedView = HoursWorkedView()
self.view.addSubview(hoursWorkedView)
It's right here where we should be setting the delegate:
let hoursWorkedView = HoursWorkedView()
hoursWorkedView.delegate = self
self.view.addSubview(hoursWorkedView)
Though if it's me, I probably add a constructor to HoursWorkedView that accepts the delegate property:
init(delegate: HoursWorkedDelegate) {
super.init()
self.delegate = delegate
}
And now we can just do this:
let hoursWorkedView = HoursWorkedView(delegate: self)
self.view.addSubview(hoursWorkedView)

I think you're getting your view and your viewcontroller mixed up: a ViewController controls things; a view just displays them. The viewController tells the view what to display.
So, you want to connect your button to the viewController -- not the view. And you don't need a custom view class or a delegate.
Set it up like this:
create a textField and a button
create an outlet for the textField
put calculateHoursWorked directly in your viewController
create an action to connect the button to calculateHoursWorked
in calculateHoursWorked, set self.textField.text to the result of the calculation (where "textField" is whatever you named your outlet)
You wouldn't use a delegate in this context because the viewController knows everything the view does. The delegate pattern is for cases where one object has no visibility into another.
EDIT:
That being said, the bug here is that the delegate isn't actually being set anywhere.
Swift Optionals (the ! and ?) help prevent cases like this. If you explicitly unwrap an optional using !, you have to make sure it's always defined. In this case, since delegate is defined as optional (?) you have to check it:
#IBAction func calculateHoursWorked(sender: AnyObject){
// Do some calculations for the hoursWorked value
// Give value
if let currentDelegate = self.delegate {
currentDelegate.sendHoursWorked(hoursWorked)
}
}

Related

How to call the same function on multiple classes?

I have an UIViewController with 4 UIButtons. A user can tap any of those UIButtons and an UIView pops up. I want to add an didAppear() and didDisappear() function on the classes which are holding the UIViews depending on the users action. How can I call didDisappear() without the use of an enum, for example:
func didDisappear(view: EnumViews){
switch view{
case view0: myClassWithView0.didDisappear()
case view1: myClassWithView1.didDisappear()
case view2: myClassWithView2.didDisappear()
case view3: myClassWithView3.didDisappear()
}
}
Now I get 4 times duplicate data. I know that function exists for my class with a UIView, but how to call it? I made a protocol:
protocol ViewProtocol{
func didAppear()
func didDisappear()
}
I made the classes which are holding the UIView's conform to that protocol. However I do not know how to use it, when I create the class I get the error:
'myClassWithUIView' cannot be constructed because it has no accessible
initializers
The classes are all in an array and I can identify which UIView needs to pop up from the sender.tag. Ideally, I want to have something like this:
#IBAction func bringNewUIView(_ sender: UIButton) {
let newView = myArrayOfClassesWithUIView[sender.tag]
newView.didAppear()
}
You've got many things going on here. I'll start with the easy one.
'myClassWithUIView' cannot be constructed because it has no accessible initializers
This just means you don't have an initializer for your class. So inside your myClassWithUIView implementation you need to have init. I can't really help you with building the init because I don't know how that class is structured, but I will assume this is something you know how to do anyway.
Your #IBAction seems fine. Once you have an array of your classes that seems like it should work. Edit your post if that is not the case.
Finally, for your didDisappear question, you can do something like this:
func didDisappear(view: EnumViews) {
//Check to see if this view conforms to your ViewProtocol (that's not a good name, btw)
if let myClass = view as? ViewProtocol {
//Since it does conform to ViewProtocol you can call didDisappear on it
myClass.didDisappear()
}
}
Alternatively, if you already know that the didDisappear function is always passing in a view that conforms to ViewProtocol why not just change the argument and make that easier?
func didDisappear(view: ViewProtocol) {
view.didDisappear()
}

Enabling two-way communication between two classes

So I have a custom view touchableView inside a ViewController.
touchableView informs ViewController of changes in its properties through a delegate protocol (ViewController being the delegate).
What is the best method to change properties of touchableView from ViewController (so the other way around)?
Is there a way to create a two-way delegate relationship between two classes?
Simply:
Assuming that you are already have touchableView instance in the ViewController, you should be able to set -or get-/call its properties and methods.
For instance, assume that you have the following method in touchableView class:
class func fromNib() -> TouchableView {
return Bundle.main.loadNibNamed(String(describing: self), owner: nil, options: nil)![0] as! TouchableView
}
You should simply be able to:
let touchableView = TouchableView.fromNib()
thus:
// for example
touchableView.myProperty = "Hello"
I assumed that TouchableView has a string property called myProperty...
Remark:
For some reason, I suggest to implement some of property observers in TouchableView:
Property observers observe and respond to changes in a property’s
value. Property observers are called every time a property’s value is
set, even if the new value is the same as the property’s current
value.
They might be -somehow- useful to be implemented in your custom class. For clarity, let's consider -for instance- that if editing the value of myProperty should be effecting the background color of the view, it might be implemented as:
var myProperty: String = "Initial Value" {
willSet {
print("About to set value to: \(newValue)")
}
didSet {
backgroundColor = UIColor.red
}
}
Further Reading:
If you are looking for an advanced approach for two way bindings (Implementing MVVM), you might want to check this article.
I would also suggest to take a look at some frameworks that will be so helpful for such an approach, such as RxSwift, for a more simple framework, you might want to check ReactiveKit/Bond.

IF Statement Incorrectly Evaluating to True when Using Optional Values in Swift

I have set up my view controllers so that they send a notification once their -viewDidLoad method is about to return. For example:
class MyViewController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
//Do Stuff
var notificationCenter = NSNotificationCenter.defaultCenter();
notificationCenter.postNotificationName("AViewControllerDidLoadNotification", object: self);
}
}
My AppDelegate class is listening for this notification and implementing the method shown in this picture.
In case the picture isn't loading, the method takes the notification sent by the view controllers as it's only argument and then tests whether the UIViewController's title property has a non-nil value. If the title property is non-nil it logs the title.
However, as you can see in the debugger panel, the title property of the view controller is nil and the if statement is still evaluating to true.
I am admittedly new to optional values. But I have recreated this situation in a swift playground and the if statement evaluates to false. Any ideas?
You've gotten yourself into rather an odd situation with your very peculiar use of the expression notification.object?.title, because notification.object is not, of itself, a UIViewController. It is an AnyObject.
Now, an AnyObject has no known properties, so it has no title and your expression, it would seem, should not even compile. But, by a special dispensation coming from certain oddities of Objective-C, you are in fact allowed to ask about an AnyObject's properties anyway. But when you do, the result is itself an Optional, because no such property might exist.
Thus, you are actually testing, not the value of a view controller's title property, but whether this unknown object has a title property in the first place; and if in fact it does have a title property at all, the value of that title property is double-wrapped inside that Optional.
To see this clearly, just test this (silly) code:
let n = NSNotification(name: "Howdy", object: "Hi")
let t = n.object?.title
Look at what type t is. It is not a String?; it is a String??. That's your double-wrapped Optional. This means that it would be an Optional-wrapping-a-String in case this object turns out to have a title property, but just in case, that value has itself been wrapped in an Optional.
Thus, your test doesn't do what you want it to do. To do what you want to do, just speak much more plainly and simply. You need to cast the object to a UIViewController first, and then examine its title. Like this:
func aViewControllerDidLoad(notification:NSNotification) {
if let vc = notification.object as? UIViewController {
if vc.title != nil {
// ...
}
}
}

iOS Delegate is returning nil (Swift)

I have a feeling there is more than one problem with this code, but my first issue is that my delegate returns nil and I do not know why. First, is my delegate:
import UIKit
//delegate to move information to next screen
protocol userEnteredDataDelegate {
func userDidEnterInformation(info:NSArray)
}
Next, I have a var defined for the delegate and I believe the ? makes it an optional variable? This is defined inside the class
var dataPassDelegate:userEnteredDataDelegate? = nil
Now, after my user has entered information into the fields in the view, I want to add those field values to an array and then pass that array on to the next view where it will be added to. I have pieced this code together from some YouTube examples but I think I am missing a needed part. When do I assign some kind of value to the dataPassDelegate var so it is not nil when the if statement comes? Do I even need that if statement?
if blankData != 1 {
//add code to pass data to next veiw controller
enteredDataArray = [enterDate.text, enterSeason.text, enterSport.text, enterDispTo.text]
//println(enteredDataArray)
self.appIsWorking ()
if (dataPassDelegate != nil) {
let information: NSArray = enteredDataArray
println(information)
dataPassDelegate!.userDidEnterInformation(information)
self.navigationController?.popViewControllerAnimated(true)
} else {
println ("dataPassDelegate = nil")
}
//performSegueWithIdentifier("goToDispenseScreenTwo", sender: self)
activityIndicator.stopAnimating()
UIApplication.sharedApplication().endIgnoringInteractionEvents()
}
blankData = 0
}
Your help is appreciated.
A delegate is a pointer to another object that conforms to a particular protocol. Often you use delegates to refine the behavior of your class, or to send back status information o the results of an async network request
When you set your dataPassDelegate delegate is up to you.
What is the object that has the dataPassDelegate property? What object will be serving as the delegate?
You need to create 2 objects (the object that will be serving as the delegate, and the object that has the dataPassDelegate property) and link them up.
We can't tell you when to do that because we don't know what you're trying to do or where these objects will be used.

Accessing an IBOutlet from another class

I am new to Swift/iOS, so please bear with me:
I am trying to access a function in one class from another class, and update an UIImage name.
Within my viewcontroller class I have
class Documents: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet var UpdateImage: UIImageView
override func viewDidLoad() {
super.viewDidLoad()
UpdateImage()
}
func UpdateImage() {
UpdateImage.image = UIImage(named: "NewImage")
}
}
Everything works, the Image gets updated to "NewImage"
Question: I can access the UpdateImage func from another class, but why is it generating an error when trying to change the image in the Documents class?
class GetChanges {
var success = { operation:AFHTTPRequestOperation!, response:AnyObject!) -> Void in
var MakeChange = Documents()
MakeChange.UpdateImage()
}
}
This generates an error on the "UpdateImage.image = UIImage(named: "NewImage")" in the Documents Class; "fatal error: unexpectedly found nil while unwrapping an Optional value"
When you call it within the class itself, it is operating on itself and it has already been created from a nib/storyboard. This means that UpdateImage exists.
When you call the method from another class, when you call this line:
var MakeChange = Documents()
You are creating a new instance of Documents. This is not initialized through the nib/storyboard, and thus it never populated the IBOutlet value UpdateImage. Because this value doesn't exist, it unexpectedly finds nil and throws an error.
You need to somehow retain a reference to the instance of Documents you're trying to display. I'd need more information to tell you how to do that.
Also, because you mentioned that you're new, I'd like to point out a few issues I notice with your code that is making it very difficult to read.
Capitalized names are reserved for Types variable names should (almost) never begin with a capital letter.
Variable names should reflect the object they represent. UpdateImage sounds like it is an image. It would be better to name this updateImageView
Functions should be lowercase as well. It is strange to see capitalization this way and makes the code a bit uncomfortable to read.
Good luck!
Read about View Contoller's lifecycle, it's very important knowledge for iOS developer.
As Logan said:
You are creating a new instance of Documents. This is not initialized through the nib/storyboard, and thus it never populated the IBOutlet value UpdateImage
This means that after call init for ViewController (i.e. Documents()) nib isn't loaded. You can use outlets of viewController in another code only after viewDidLoad stage. Apple docs:
The nib file you specify is not loaded right away. It is loaded the first time the view controller's view is accessed. If you want to perform additional initialization after the nib file is loaded, override the viewDidLoad() method and perform your tasks there.
You can remove MakeChange.UpdateImage(), because it will be called in viewDidLoad. Or, if you want pass specific image name to view controller:
class Documents: UIViewController, UITableViewDataSource,
UITableViewDelegate {
#IBOutlet var UpdateImage: UIImageView
var imageName: String?
override func viewDidLoad() {
super.viewDidLoad()
updateImageView()
}
func updateImageView() {
if let imageName = imageName {
UpdateImage.image = UIImage(named: imageName)
}
}
}
After that, you can use
let documentsViewController = Documents
documentsViewController.imageName = "newImage"
When you load documentsViewController, newImage will be presented

Resources