Swift4 - Unexpectedly found nil - Setting Image to Button [duplicate] - swift

This question already has answers here:
Calling reload Sections from AppDelegate
(2 answers)
Closed 4 years ago.
Hi guys I want to make a helper class, which sets all my pictures to my buttons. But always the picture is nil :( Where is my mistake?
class 1 (Where action happens)
class RandomClass {
let imageSetter = ImageSetter()
let mvc = MainViewController()
func actionWhenConnected(){
imageSetter.setButtonImages(nameOfButton: mvc.ConnectingButtonLabel, nameOfImage: "Connection/ConnectionOn", nameOfColor: .green, forStatus: .normal)
}
}
This means when the function is called, the image in my MainViewController should switch to my green Connection On Picture
class 2 (Helper Class)
class ImageSetter{
func setButtonImages(nameOfButton:UIButton, nameOfImage:String, nameOfColor:UIColor, forStatus:UIControl.State){
nameOfButton.setImage(UIImage(named: nameOfImage), for: forStatus)
let origImage = UIImage(named: nameOfImage)
let tintedImage = origImage?.withRenderingMode(.alwaysTemplate)
nameOfButton.setImage(tintedImage, for: .normal)
nameOfButton.tintColor = nameOfColor
}
}
Assets
In my MainViewController it works :(
It seems that RandomClass has no access to assets.xcassets because it doesnt find the name of the image?
SOLVED!
func actionWhenConnected(){
let mvc = UIApplication.shared.keyWindow?.rootViewController as! MainViewController
imageSetter.setButtonImages(nameOfButton:mvc.ConnectingButtonLabel, nameOfImage: "Connection/ConnectionOff", nameOfColor: .red, forStatus: .normal)
}
Big thanks #matt

The problem is this line:
let mvc = MainViewController()
Think about what the phrase MainViewController() does! It creates a new separate second instance of MainViewController. Thus the view never loads from the storyboard, the outlet to the button is never hooked up, and you crash.
What you want is a reference to the actual MainViewController instance whose view already exists in your interface. That is the one with the button you can see.
In my MainViewController it works
Yes, because in MainViewController you know how to get a reference to this instance — you just say self. But even there it wouldn’t work if you said MainViewController().
It seems that RandomClass has no access to assets.xcassets because it doesnt find the name of the image?
Yes it does. It finds the image. What is nil is the button.

Related

Swift macOS SegmentedControl Action not getting called

Description
I am trying to use NSSegmentedControls to transition between Child ViewControllers. The ParentViewController is located in Main.storyboard and the ChildViewControllers are located in Assistant.storyboard. Each ChildViewController has a SegmentedControl divided into 2 Segments and their primary use is to navigate between the ChildViewControllers. So they are set up as momentaryPushIn rather than selectOne. Each ChildViewController uses a Delegate to communicate with the ParentViewController.
So in the ParentViewController I added the ChildViewControllers as following:
/// The View of the ParentViewController configured as NSVisualEffectView
#IBOutlet var visualEffectView: NSVisualEffectView!
var assistantChilds: [NSViewController] {
get { return [NSViewController]() }
set(newValue) {
for child in newValue { self.addChild(child) }
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
addAssistantViewControllersToChildrenArray()
}
override func viewWillAppear() {
visualEffectView.addSubview(self.children[0].view)
self.children[0].view.frame = self.view.bounds
}
private func addAssistantViewControllersToChildrenArray() -> Void {
let storyboard = NSStoryboard.init(name: "Assistant", bundle: nil)
let exampleChild = storyboard.instantiateController(withIdentifier: "ExampleChild") as! ExampleChildViewController
let exampleSibling = storyboard.instantiateController(withIdentifier: "ExampleSibling") as! ExampleSiblingViewController
exampleChild.navigationDelegate = self
exampleSibling.navigationDelegate = self
assistantChilds = [exampleChild, exampleSibling]
}
So far so good. The ExampleChildViewController has an NSTextField instance. While I am in the scope of the TextField, I can trigger the action of the SegmentedControls. Its navigating forward and backward as it should. But once I leave the scope of the TextField I can still click the Segments, but they are not triggering any action. They should be able to navigate forward and backward even if the TextField is not the current "First Responder" of the application. I think I am missing something out here, I hope anyone can help me with this. I know the problem is not the NSSegmentedControl because I am seeing the same behavior with an NSButton, which is configured as Switch/Checkbox, in the SiblingViewController. I just don't have any idea anymore what I am doing wrong.
It`s my first time asking a question myself here, so I hope the way I am doing is fine for making progress with the solution. Let me know if I can do something better/different or if I need to provide more information about something.
Thanks in advance!
Additional Information
For the sake of completeness:
The ParentViewController itself is embedded in a ContainerView,
which is owned by the RootViewController. I can't imagine this does
matter in any way, but this way we are not missing something out.
I am actually not showing the navigation action, because I want to
keep it as simple as possible. Furthermore the action is not problem,
it does what I want it to do. Correct me if I am wrong with this.
Possible solutions I found while researching, which did not work for me:
Setting window.delegate of the ChildViewControllers to NSApp.windows.first?.delegate
Setting the ChildViewController to becomeFirstResponder in its func viewWillAppear()
visualEffectView.addSubview(self.children[0].view, positioned: NSWindow.OrderingMode.above, relativeTo: nil)
Related problems/topics I found while researching:
Basic segmented control not working
Adding and Removing Child View Controllers
NSSegmentedControl - Odd appearance when placed in blur view
How to set first responder to NSTextView in Swift?
How to use #selector in Swift 2.2 for the first responder
Accessing methods, actions and/or outlets from other controllers with swift
How to use Child View Controllers in Swift 4.0 programmatically
Container View Controllers
issues with container view
Control a NSTabViewController from parent View
How to detect when NSTextField has the focus or is it`s content selected cocoa
SOLUTION
let parentViewControllerInstance = self.parent as! ParentViewController
segmentedControl.target = parentViewControllerInstance
In my case I just had to set the delegate as the target of the sendAction method.
Background
Ok, after hours of reading the AppKit Documentation I am now able to answer my own question.
First, debugging the UI showed that the problem was definitely not in the ViewHierarchy.
So I tried to think about the nature of NSButton and NSSegmentedControl. At some point I noticed that both are subclasses of NSControl.
class NSSegmentedControl : NSControl
class NSButton : NSControl
The AppKit Documentation says:
Discussion
Buttons are a standard control used to initiate actions within your app. You can configure buttons with many different visual styles, but the behavior is the same. When clicked, a button calls the action method of its associated target object. (...) You use the action method to perform your app-specific tasks.
The bold text points to the key of the solution – of its associated target object. Typically I define the action of an control item like this:
button.action = #selector(someFunc(_:))
This causes the NSControl instance to call this:
func sendAction(_ action: Selector?, to target: Any?) -> Bool
Parameter Description from the documentation:
Parameters
theAction
The selector to invoke on the target. If the selector is NULL, no message is sent.
theTarget
The target object to receive the message. If the object is nil, the application searches the responder chain for an object capable of handling the message. For more information on dispatching actions, see the class description for NSActionCell.
In conclusion the NSControl instance, which was firing the action method (in my case the NSSegmentedControl), had no target to send its action to. So it was only able to send its action method across the responder chain - which obviously has been nil while the first responder was located in another view.

MTKView delegate does not work [duplicate]

This question already has answers here:
Swift can't call protocol method via delegate
(2 answers)
Closed 4 years ago.
I am writing a simple metal app.
I tried to separate view and render. So I write a MTKViewDelegate class to to do render work and set the view's delegate to it. But it does not work. But when I make the view controller instance as the delegate, it works.
blow is my code
class ViewController: UIViewController,MTKViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let view = self.view as! MTKView
//view.delegate = self //this works,print 'drawing'
view.delegate = ViewController() //this does not work, not print anyting
print("view did load")
}
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
print("change size")
}
func draw(in view: MTKView) {
print("drawing")
}
}
Why this happens?
update
What I really meant was ,why when I set current instance of ViewController as view's delegate , it worked, but when I create a new instance, it did not work.
What I really want to do is making a render class to do render work as MTKView's delegate. But it did not work, so I test it with ViewController.
Thanks for matt's answer,I knew the reason is that , the delegate property of MTKView is weak referenced, so it could not hold the new created instance of MTKViewDelegate. The new created instance is recycled by garbage colletor.
What I need was just that the delegate property of MTKViewDelegate is 'weak'.When people do not notice this, the situation is confusing.
So,I think my question is valuable, and it's not like question Swift can't call protocol method via delegate. I think I should not be blocked to ask questions again.
The problem is that this line:
view.delegate = ViewController()
...makes a second ViewController, makes it the delegate, and then throws it away.
You (the class containing this code) are the ViewController you want to be the delegate. So you need to say:
view.delegate = self
It works when you say that because self is a persistent object. The other ViewController() comes into existence and just vanishes again; it has no persistence so it can't do anything.
Why do you think it's wrong to say view.delegate = self? It's normal and it works.

Opening window + view from an other view controller

I've got a ViewControllerOne. ViewControllerOne is connected via Ctrl-Drag (in storyboard) to a menu-button mBtn (which means I don't know how it is implemented programmatically).
Clicking on this mBtn, a ViewOne appears (present modal). This ViewOne is bound to ViewControllerOne. ViewOne has a button btnOne.
Clicking on btnOne I want ViewOne to be dismissed and ViewTwo to be shown. ViewTwo belongs to ViewControllerTwo and to WindowControllerTwo.
The WindowControllerTwo-ViewControllerTwo-binding is the standard case as created on a new project.
I have the following code in the IBAction for button btnOne in ViewControllerOne:
#IBAction func onbtnOnePressed(sender: AnyObject){
let m_WindowControllerTwo = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"), bundle: nil).instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("WindowControllerTwo")) as! NSWindowController // I have no custom class for the window controller as I don't really know what I can use it for ...
let m_ViewTwo = WindowControllerTwo.contentViewController as! ViewControllerTwo // my custom class for ViewTwo
m_ViewTwo.attributeIWantToPassToThisView = self.x // an attribute I want to pass from view a to view b
m_WindowControllerTwo.contentViewController = m_ViewTwo // passing the attribute from a to b
m_WindowControllerTwo.showWindow(self) // this does not work
self.dismiss(nil) // see NOTE
}
This code actually does not work. On debugging it step by step, I'm seeing the window/view flickering but not appearing...
NOTE: I could connect the button btnOne with a ctrl-drag to ViewControllerTwo. This works. But then the current ViewOne does not get dismissed!
Question: What am I doing wrong here? In iOS swift this also works. I don't quite get the WindowController stuff, so I'll need your advice on this.
Instead of this: m_WindowControllerTwo.showWindow(self)
use:
let application = NSApplication.shared()
application.runModal(for: wordCountWindow) //this will present WindowControllerTwo modally.
then to close your present controller add this line: PresentWindowControllerName.close()

in swift, ViewController wont let me access its textfield from another class

here is what is happening https://imgur.com/a/fblot
yes i've read the article about optional
but that textfield isn't nil or without a value as it does it
when i press enter when theres a value in the text-bar
i also tried ! and ? with same results
i tried many many things and it wont work
always the same crash
can someone explain what is the reason that textfield is hard to access
i tried
var = ViewControler()
If you want to call the class function from the viewcontroller that contains the textfield change the class function to have one argument :
func changetextfieldincontroller(_ vc: NSViewController){
if let vc = vc as? ViewController{
vc.textField.stringValue = "New text"
}
}
when you call it from the controller make sure to pass self as an argument.
You need to instantiate the viewController since it looks like it's from a storyboard/xib. If that's the case, you can check this other question: How can I load storyboard programmatically from class?

UIView is nil after moving to the selected index via UITabBarController

I am using a UITabBarController in a music playing app. I have 2 indexes in my tabBarController, index 0 (tableViewController) and index 1 (nowPlayingViewController). Index 0 contains a UITableViewController that displays song names, and index 1 contains a UIViewController that is supposed to display the now playing page, including labels of the current playing song and artist.
When a user taps on a cell (a song name) on tableViewController i do two things, first I give the title of the song that was selected to a third class, a ControllerClass and then I move the tab bar over to index 1, which is the now playing page...
//this is inside tableViewController class
let _ControllerClass = ControllerClass()
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
_ControllerClass.nowPlayingSongInfo(trackTitles[indexPath.row])
self.tabBarController?.selectedIndex = 1
}
So far so good. The nowPlayingViewController is the delegate of the ControllerClass. When ControllerClass receives the new song title, it calls a delegate method that is supposed to set the text of a UILabel on the nowPlayingViewController. Here is the method inside ControllerClass that calls back with the new title as a String.
func newNowPlayingSongTitle(songTitle: String){
delegate?.configureLabels(songTitle)
}
this also works fine, all good. I receive the callback successfully on nowPlayingViewController which I know because I am able to print the song title when the callback is received, like so...
func configureLabels(songTitle: String){
print("song title is \(songTitle)")
//successfully prints the correct song title
}
HOWEVER, my issue is this... I need to not just print out the new song title, I need to set my UILabel's text property on my nowPlayingViewController equal to the new song title that i receive in the callback. BUT, when i try this...
func configureLabels(songTitle: String){
print("song title is \(songTitle)")
self.songLabel.text = songTitle
//crashes on the line above error = "unexpectedly found nil while unwrapping an optional value"
}
the app crashes due to unexpectedly found nil while unwrapping... . Apparently the UILabel is nil, even though I know i have set it up properly since I can set its text property from other places.
I also tried this,
func configureLabels(songTitle: String){
let view = UIView(frame: CGRectMake(100, 100, 100, 100))
self.view.addSubview(view)
}
to see if the entire view itself is nil, and again it crashes with the same error, so indeed self.view is nil.
What is going on here? I am able to set the labels and images in this view from elsewhere, however in the situated illustarted above the UILabel and the entire view itself are nil. How to fix this problem? Any suggestions would be great, thank you.
Note: I have tried self.loadView() but that did not help either
My issue was that my delegate was set to a different instance of nowPlayingViewController, than the instance that was currently in the tabBarController. To fix this, instead of doing
let np = nowPlayingViewController()
ControllerClass.delegate = np
I did
let np = self.tabBarController?.viewControllers![1] as! nowPlayingViewController
then ControllerClass.delegate = np
the reason this is necessary with a UITabBarController is because the views that are embedded within the tabBarController are loaded once and then remain loaded, thus I needed to set the UILabels on the nowPlayingViewController for the proper instance of the nowPlayingViewController, which is the one that was embedded in the tabBarController, since that instance was the one being displayed. Sure hope someone else finds this useful!