UIView is nil after moving to the selected index via UITabBarController - swift

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!

Related

Update UITableView Data cell From Another View Controller

I am trying to update my firstVC "like Button" Status once the user enters the SecondVC and likes or unlikes the heart for the cell they entered. I've tried a ton of different ways today but none seem to work properly. Any help or ideas
Basically. The user can click on a cell, then gets directed to another view controller which they also can change the status of the like button in there. Once they return back to the mainVC I want the like button to display the change.
I'm wondering if I am suppose to save the cells data locally in the phone or just send variables back and forth to each view controller. But if I send variables back and forth, how do I send a variable back to the first VC to the specific cell and let it know that the cell like button is no longer liked. I hope I make sense. Thank you all in advance. Still new to swift here :)
I think you can use the delegate pattern
protocol ViewController2Delegate: class {
func buttonStatusDidChange(_ viewController2: ViewController2, to status: Bool)
}
Add this property to the second view controller:
weak var delegate: ViewController2Delegate?
When the button status change, call:
delegate?.buttonStatusDidChange(self, to: true)
Make the first view controller conform to the protocol:
extension ViewController1: ViewController2Delegate {
func buttonStatusDidChange(_ viewController2: ViewController2, to status: Bool) {
// change the button according to the status
}
}

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.

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

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.

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?

NSUserDefaults changing every segue

Sorry for the newbie question but i am really stuck.
I have a UIViewController and 4 tableviews in an app. When i click on a button on the UIViewcontroller it segues to a UITableviewController called "Beach". When the user clicks on a cell of the table, it segues back to the UIViewController and displays the selected cells title as the buttons title. The problem that i am having is when i click on a nother button to a tableview and then clicks on the cell, the previous buttons title sets back to the previous title.
i have a prepare for segue function in the tables view controllers and this returns the selected table title (named : Titleoftable) to the main VC which, the strings.
the way i am currently doing it is to make a NSUserDefault below but the problem still remains the same - The value changes top "" every time i click on another table V---
let path = tableView.indexPathForSelectedRow
let cell = tableView.cellForRowAtIndexPath(path!)
let persistenceStoreKey = "MyStringKey"
let stringToStore = "\((cell?.textLabel?.text!)!)"
// Store data
NSUserDefaults.standardUserDefaults().setObject(stringToStore, forKey: persistenceStoreKey)
NSUserDefaults.standardUserDefaults().synchronize()
// Get data
let myStringt = NSUserDefaults.standardUserDefaults().stringForKey(persistenceStoreKey)
destination.textOfBeach = (myStringt)!
destination.isBeachSelected = true
}
I have been stuck on this problem for ages now! PLEASE HELP!!
PS- I am using swift-2 and Xcode7
Make sure that you are not pushing a new ViewController after you click on the tableview's cell. It gives me the impression that it might be the case since you are seeing only the title of the last cell you pressed.. Make sure you actually pop the tableview's controller instead of pushing a new one.
If you want to keep using NSUserDefaults, you could just call
navigationController?.popViewController(animated: true)
in your cellSelection function, and read the defaults value in the viewWillAppear of the main ViewController.
override func viewWillAppear(_ animated: Bool) {
let userDefault = NSUserDefaults.standard()
if let beachSelection = userDefault.string(forKey: "BeachControllerSelection") {
textOfBeach = beachSelection
isBeachSelected = true
}
//same for the rest of the other table view's selections
}