Can't update Label from other class in Swift - swift

I have a little problem with my code so that I can't call a function. I created a function func changeLabelText(text: String) { mylabel.stringValue = text } in the ViewController with that I can update the Text in the Label. When I try to call the function from another class in the Project, I get a runtime error(EXC_BAD_Instr...) and the Debugger holds on the line where I try to change the Label's text, with the error: fatal error: unexpectedly found nil while unwrapping an Optional value. Where is the problem? Can someone help me please!

Regarding the "unexpectedly found nil while unwrapping an Optional value" error, the problem is that you have an optional (judging from the code snippet you've provided, it must be an implicitly unwrapped optional) that is nil. Most likely, mylabel is nil. Either print the value or add a breakpoint and examine the property in the debugger to confirm.
If it is nil, you then have to figure out why. In our discussion, it turns out that you're trying to call the Log event in the view controller:
ViewController().Log("asdf")
The problem is that this doesn't call Log in the existing view controller, but rather the ViewController() expression ends up instantiating a new, completely unrelated view controller with its outlets not hooked up to anything. Thus the attempt to update the implicitly unwrapped outlet will cause the error you shared with us.
If you want this separate class (a database manager object) to inform the view controller of event in order to allow the view controller to update the UI, there are three common approaches:
Completion/progress handlers that are closures/blocks.
This is used for simple interface where the database needs to inform view controller when the request is done.
Delegate-protocol pattern.
The delegation pattern (usually conforming to some well-established protocol) is used for rich interfaces where the database needs to inform the view controller of a variety of different types of events.
Notification pattern.
Notifications are used when you want a loosely coupled interface between the database object and whatever is handling these notifications. The view controller might register itself as an observer of any notifications of a particular name with the defaultCenter() of the NSNotificationCenter. The database object can then post notifications of that name (supplying details via the userInfo dictionary) and the view controller will be informed of these events.

A few things are you calling an instance of the view controller properly
e.g if the class name is ViewController, in the the view controller that you are calling the function make sure you use
let VC = ViewController ()
and then use VC.yourFunctionName
also instead of stringValue use .text

Related

Non optional IBOutlet returns Fatal error: when referenced from another UIViewController class

I have a UIView defined in
class InteractViewController: UIViewController {
#IBOutlet weak var tipsView: UIView!
var tipslayer: CALayer {
return tipsView.layer
}
}
There are a number of constraints also attached in the class as well as labels for example.
#IBOutlet weak var tips1Top: NSLayoutConstraint!
#IBOutlet weak var tips1Right: NSLayoutConstraint!
#IBOutlet weak var tipsTitle: UILabel!
#IBOutlet weak var tipsDesc: UILabel!
All are connected on the storyboard to their correct outlets. Referencing these within the actual InteractViewController itself I am able to adjust opacity, update labels on the layer etc.
However, these are not needed unless the user is first using the application so a simple if else on the InteractViewController calls a function to either display and update properties of these or not show them at all.
The function that controls and updates these properties is in another class (Swift file in the project called.
class TipsViewController: UIViewController {
func tips() {
InteractViewController().tipslayer.transform = CATransform3DMakeScale(0.8, 0.8, 1.0)
}
}
Causes the app to crash creating
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
on
return tipsView.layer (the InteractViewController)
tipsView and tipslayer are of cause not optionals and are not declared as optionals.
I have three questions I hope I can get assistance with.
Is having the physical reference to the outlets on the ViewController class InteractViewController: UIViewController the problem? If so is it because they are supposed to be programmatically added instead on the class TipsViewController: UIViewController (which does not have a physical visual view controller in Xcode)?
Does having a physical reference to the outlets on the
class InteractViewController: UIViewController
create unnecessary memory use if on InteractViewController the call to the function on the second ViewContoller TipsViewController.tips() is never called so in other words the answer to question 1 is essentially any physical outlets should be programmatically added on lass TipsViewController: UIViewController?
If there is no memory issue and knowing that tipsView and the returning tipslayer are not optionals why am I simply unable to reference them from the tips() function in the other class (TipsViewController: UIViewController)? I have seen multiple posts about "passing data between view controllers" but I am not passing data between variables or arrays. I want/ need to simply update label string, constraints (objects in one view controller class from another view controller class.
I know people are busy so just to say in advance I appreciate all feedback and assistance with the problem.
It was suggested that my question was a duplicate of [What does "fatal error: unexpectedly found nil while unwrapping an Optional value" mean?
But this is not my question. MY question relates specifically to accessing an Outlet from another ViewController which is then generating a Nil response to the Outlet because as explained InteractViewController is apparently not referencing the ViewController. Please read the explanations and answer provided by Rob which answers this specific question.
Your title says "IBOutlet returns Fatal error: when referenced from another UIViewController class". The bottom line is that one view controller should never access another view controller's outlets.
In your code snippet, you employ the InteractViewController() syntax. Unfortunately, this just instantiates view controller, but does not hook up outlets in storyboard. You need to instantiate via storyboard and, even then, you can’t reference your outlets immediately, but only after viewDidLoad is called later. Only the view controller itself should be trying to access its own outlets, anyway.
The TipsViewController() or InteractViewController() syntax says "create a new, blank instance of that view controller". It's equivalent to saying TipsViewController.init() or InteractViewController.init(). And, worse, it's not using the storyboard at all (and thus no outlets will be hooked up). That's not what you want. You should avoid using this ViewController() syntax, as outlets will never been accessible that way.
InteractViewController should determine whether TipsViewController should be presented (e.g. checking user defaults), and if so, present it. The only thing InteractViewController should do, though, is:
figure out whether to present TipsViewController;
if so, present that TipsViewController; and
pass model data (e.g. whatever InteractViewController retrieved from UserDefaults) to the TipsViewController instance that is created for you.
But anything and everything about how TipsViewController presents its subviews, is the purview of TipsViewController alone. InteractViewController should never interact directly with any outlets of TipsViewController.

Though I did optional binding, I got error “fatal error: unexpectedly found nil while unwrapping an Optional value"

I received data from my application server, and then assign that data to ios's object(articles).
And using that object(articles), I save values to cell's label in tableView.
After that, when user click the cell of tableView, this app move to another ViewController(NoticeArticleVC). Before moving, I assign value of object(articles) to that ViewController's variable like below code.
However, as usual, I did optional binding using 'if let' statement. But I got error as if I didn't do optional binding.
Though I cleaned build folder, and then rebuilded, it didn't work.
Please let me know what's wrong with my code.
You've instantiated the new ViewController, but I don't think titleLabel will be set until the view has loaded. You will need to present the new view controller and then set the label. Or better, set a variable on the new ViewController and only set the label field from within that VC code (e.g. in/after viewDidLoad).

Binding in Xcode 8 and Swift 3: Target never gets called and argument is passed as uninitialized

Here's 2 binding issues I'd like to share with you, related to selector name and parameters when configured in IB.
I have CoreData objets retrieved through an ArrayController and populated in a NSTableView. All the binding is made through Interface Builder. Each row contains a button that should check an url (one of the properties of the entity).
1. To make the button fire, I first tried to bind it this way:
For the target:
Bind to: File's owner (in this case, a ViewController)
Model Key Path: self
Selector Name: revealInFinder:
For the argument:
Bind to: Table Cell View
Model Key Path: ObjectValue
Selector Name: revealInFinder: (<automatically set>)
When I click the button, the function revealInFinder(object: NSManagedObject) that I wrote in the ViewController is simply never called: I tried with or without argument, with or without a colon, nothing happens: no breakpoint triggered, no message in the console.
I can put random chars in the selector name, I don't even get a
"unrecognized selector name"!
... Same thing if I change the name of the function in the ViewController, or simply remove it: Nothing happens and no errors in console (of course, I've checked: the owner of the File is set to be the ViewController, but the view wouldn't show up anyway).
As the binding are made in IB and not in code, it's difficult to trace and debug my mistakes (I also put -NSBindingDebugLogLevel 1 to the “Arguments to be passed at launch”, as recommended here: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CocoaBindings/Concepts/Troubleshooting.html). But how am I supposed to handle something that is apparently a "non-event"??
Sure I'm missing something here but what?
2-I've decided to bind the button in a different way:
For the target: (without argument at first)
Bind to: Table Cell View
Model Key Path: ObjectValue (so the Core Data's entity, right?)
Selector Name: revealInFinder:
This works, as I created NSManagedbject subclasses for my entities, and wrote extensions for them.
The problem occurs when I add an argument, as I need to get the managedObjectContext to do some fetching inside the function:
For the argument:
Bind to: File's owner
Model Key Path: self.managedContext (the moc is set in the viewController)
After a bunch of "unrecognized selector sent" messages, I managed to write this function (not really sure why it's only called with the "_"):
func revealInFinder(_ managedContext: NSManagedObjectContext)
but the passed argument remains 0x00000000... (<uninitialized> in the console).
I changed the Model Key Path by removing the self., and then bound it to the managedObjectContext of the ArrayController instead):
Bind to: Array Controller
Controller Key: ArrangedObjects
Model Key Path: managedObjectContext
The moc still remains uninitialized.
Why is my first try not triggering any action?
Why the argument is uninitialized in the second try (not even "nil")?

Unexpectedly found nil for optional value - which is not nil after optional chaining

I've an array of images in CoreData, which is fetched and used in the Master View Controller and passed to the Detailed View Controller and again to a Container View Controller. The images are successfully shown in the Master and Detail controllers, but not the Container View Controller.
Within the Container View Controller, I am using conditional binding to unwrap the image in cellForItemAtIndexPath.
In the debugger, print("did actually: (images.count)") on load shows I've an image in the array, and imageForCell is not nil - it is a UIImage - BUT, I still get unexpectedly found nil while unwrapping an Optional value...
So to give the answer to this question, first always check what is nil. You assumed the value you unwrapped was nil, but your view was actually nil.
A tip: use the gaurd statement. Inside the guard statement you have to specify what your code has to do in an error situation. After the guard statement, you can use the values that you unwrapped. Your current code just shows an empty cell without any messages when your image is nil. If it would not show an image, you would not know what went wrong.
Personally I like to check every possible optional in my methods. This makes methods annoyingly large, but prevents errors. I also like to specify the type of the object, but that is entirely up to you. For me it's mostly the difference between "UIImage" and "UIImage?". In your situation the UIImage might still be nil.
To continue to your next question, why it still wasn't working. So I looked around at how a UICollectionView would initialize its cells when loaded from the storyboard. People said that when manually using registerClass in those scenario's it would overwrite the view loaded from the storyboard. See: How to set a UILabel in UICollectionViewCell. I asked you to remove it, to confirm my suspicions.
I'm assuming you only need to use registerClass when not loading a view from a storyboard or nib/xib.

Class method (NSString type) called from viewcontroller returns nil?

I'm a c++ programmer new to objective-c.
I created a calculator app that is working fine using a single view. I have a Calculations class and a ViewController. Every time a button is pressed, an IBAction method in the ViewController calls methods defined in the Calculations class to handle the input and returns the output as an NSString which I then set as the value of the label.text field.
Now I am working on a tab bar app using the same Calculations class. This app has two tabs, each with a unique set of input buttons for the calculator (both views sharing the same input/output data). The first tab is identical to my first app with the single view, so I am trying to do this in a similar fashion.
Here is the problem:
When a button is pressed, the IBAction method that handles the input runs through the calls to the Calculations class methods (shown below) without error:
-(IBAction)readInput:(id)sender {
[_calculations input:[sender titleForState:UIControlStateNormal]];
inputField.text = [_calculations inputDisplay];
outputField.text = [_calculations outputDisplay];
}
however, both the inputDisplay and outputDisplay methods return nil. Using the debugger I noticed that I am unable to "step into" the calls to _calculations methods, instead the line is skipped and the value returned by both is nil. I added the following method:
-(IBAction)setNumber:(id)sender {
NSString *button =(NSString *)[sender titleForState:UIControlStateNormal];
inputField.text = button;
}
and if I attach this to the input buttons I can see the display updated. This seems to be an issue with calling the _calculations member functions and tab bar views (because this issue is not present using a single view).
I realize that I left out a lot of information, but I did it to avoid providing irrelevant information. I will provide all details that are necessary if asked.
Check to make sure _calculations is not nil.
You can send any message (call any method) on nil and it will just return nil, not cause an exception.
Without seeing more code it is going to be a bit difficult to diagnose.
If I was trying to debug this issue I would first make sure _calculations points to the object you want it to point to. If its loaded from a NIB then it might not be getting initialised, and still be nil. You can send messages to nil objects without any issues. If an object receives a message that it cant handle (the method doesn't exist, or the target object is nil) then the return for that call will be nil.
I have in the past put initilization code into the init: method, and spent a few hours why it wasn't being called, until it dawned on me that I needed to put my init code into the viewDidLoad:, or the initWithNibName:bundle: or even the initWithCoder: selector.
HTH, Matt