ios Swift ViewController Loaded from Selecting TableView Cell Loses Reference - ios

Full code for this branch here
View controller "MovieDetailsVC" is presented to the navigation controller when a cell is selected.
The presenting view controller, "ViewController", stores the row of the tableView to display in NSUserDefaults as an Int.
"MovieDetailsVC" reads the row ok. It then pulls the whole array of custom class info from CoreData and stores the array row in a property.
The data is displayed ok at first. The IBOutlet connections are setup ok. I've disconnected and reconnected twice all outlets on MovieDetailsVC, so that should be ok.
"viewDidLoad" is called a successive time. Not sure from where. When it is called, the coredata entity and row number are pulled ok.
The issue is at line "lblTitle.text = self.movieRecord.title". I'm assuming any of the IBOutlets would cause the same issue.
The error thrown is what you would see if the outlets were not connected:
fatal error: unexpectedly fond nil while unwrapping Optional value.
code for the MovieDetailsVC is below. Any ideas why this outlet link would break after working ok would be greatly appreciated.
import UIKit
import CoreData
class MovieDetailsVC: UIViewController {
#IBOutlet var lblTitle: UILabel!
#IBOutlet var lblDescr: UILabel!
#IBOutlet var lblLink: UILabel!
#IBOutlet var imgMovie: UIImageView!
var movieRecord:FavMovie!
var favMovies = [FavMovie]()
override func viewDidLoad() {
super.viewDidLoad()
fetchAndSetResult()
}
func fetchAndSetResult() {
let app = UIApplication.sharedApplication().delegate as! AppDelegate
let context = app.managedObjectContext
let fetchRequest = NSFetchRequest(entityName: "FavMovie")
do {
let results = try context.executeFetchRequest(fetchRequest)
self.favMovies = results as! [FavMovie]
} catch let err as NSError {
print(err.description)
}
if let row = NSUserDefaults.standardUserDefaults().objectForKey("movieRow") as? Int {
self.movieRecord = self.favMovies[row]
configureCellDescr()
}
}
func configureCellDescr() {
lblTitle.text = self.movieRecord.title
lblDescr.text = self.movieRecord.descrWhyGood
lblLink.text = self.movieRecord.linkImdb
imgMovie.image = self.movieRecord.getImg()
}
}

I just have a look at your source code in github and find the problem. There are two issues and I will explain it following.
it does that the second time that the app overrides viewdidload
The reason that your code would call the viewDidLoad method twice is because you have a segue in your storyboard that connect the tableViewCell to movieDetailVC. This will present the movieDetailVC once you click the cell.
And in your code for didSelectCell method, you create another movieDetailVC object and present it.
So actually movieDetailVC would be presented twice when you click the cell. This cause the issue.
Any ideas why this outlet link would break after working ok would be greatly appreciated
The reason why the IBOutlet is nil is because of the way you present movieDetailVC in your code. You create the movieDetailVC object using: let movieDetailsVC = MovieDetailsVC(). Doing it this way, the IBOutlets will not be connected correctly, because ios dont know about the storyboard information.
The correct way to create a MovieDetailVC object is to instantiate from storyboard. See this post for difference.
Solution
There is a very simple solution for your code design:
just remove let movieDetailsVC = MovieDetailsVC() and self.navigationController?.presentViewController(movieDetailsVC, animated: true, completion: nil) from your ViewController class. Since you save the selection data in NSUserDefault, the cell segue will present movieDetailVC and your movieDetailVC can also get the selection data from userDefault.

Related

Connect Outlets to Tab View Controller Result in Nil Error

I've created a new window controller that hosts a tabViewController inside my app. I've added classes to the window controller, and have the same class across all the view controllers in the tab view.
I can connect buttons and give them an action and it works perfectly, however, I try to connect an outlet and attempt to change something via the outlet, it returns nil and crashes the program. For example, this exact code works in the pre-made viewController of my app, but returns:
Unexpectedly found nil while implicitly unwrapping an Optional value
when running it through the new tab view controller I created.
What's weird to me is I can use a regular view controller and the outlets connect fine, but if I want to use a tab view controller, these nil errors are happening.
I made sure that the nil was not related to grabbing the inputs for the button by printing the audio devices, and the audio devices are there and able to be printed. It seems as if the button is not there even though it is connected.
I have also tried to simply change an NSTextField color after connecting it to an outlet and this returns the same nil error.
Any idea what I might be doing wrong here? Thanks so much for the help.
class FirstLaunchViewController: NSViewController {
var FLWindow: FirstLaunchWindowController?
var selectedAudioDevice = [String]()
#IBOutlet weak var deviceListPopUpButton: NSPopUpButton!
#IBOutlet weak var visualizeButton: NSButton!
override func viewDidLoad() {
super.viewDidLoad()
self.preferredContentSize = NSMakeSize(self.view.frame.size.width, self.view.frame.size.height)
populateDeviceList()
}
#IBAction func CloseStartupGuide(_ sender: Any) {
self.view.window?.windowController?.close()
}
#IBAction func popUpDidChange(_ sender: Any) {
print("changed")
}
//grab inputs for button
fileprivate func populateDeviceList() {
deviceListPopUpButton.removeAllItems()
for device in AudioDevice.allInputDevices() {
var teststring = ""
teststring = device.uid!
print(device.name)
deviceListPopUpButton.addItem(withTitle: device.name)
deviceListPopUpButton.lastItem?.tag = Int(device.id)
selectedAudioDevice.append(device.uid!)
}
}
}
}

When trying to segue to a view controller from a table view i get this error: Unexpectedly found nil while unwrapping

I have a segue named "hydrogenSegue" from a "hydrogenBoxButton" to a "Hydrogen" view controller. However, I also wanted to implement a table view so I could search for an element. I tried to make the code so when the cell is clicked it will segue over to the element's view. I used hydrogen as an example here.
In my main ViewController.swift file, I have this to transfer the data:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//If identifier equals the hydrogen element go to the elements Swift file
if segue.identifier == "hydrogenSegue" {
let hydrogenAtomicNumberPassing = segue.destination as! hydrogenViewController
hydrogenAtomicNumberPassing.hydrogenAtomicNumberPassed = hydrogenAtomicNumber
let hydrogenAtomicMassPassing = segue.destination as! hydrogenViewController
hydrogenAtomicMassPassing.hydrogenAtomicMassPassed = hydrogenAtomicMass
}
}
In the hydrogenViewController.swift file I have this:
import UIKit
class hydrogenViewController: UIViewController {
var hydrogenAtomicNumberPassed: Int!
var hydrogenAtomicMassPassed: Float!
#IBOutlet weak var hydrogenInformationLabel: UILabel!
#IBOutlet weak var hydrogenAtomicNumberLabel: UILabel!
#IBOutlet weak var hydrogenAtomicMassLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
//Setting the background color
self.view.backgroundColor = UIColor.gray
//Converting hydrogen's atomic number from an Int to a String
let hydrogenAtomicNumberString = String("\(hydrogenAtomicNumberPassed!)")
hydrogenAtomicNumberLabel.text = "Atomic Number: \(hydrogenAtomicNumberString)"
//Converting hydrogen's atomic mass from a Float to a String
let hydrogenAtomicMassString = String("\(hydrogenAtomicMassPassed!)")
hydrogenAtomicMassLabel.text = "Atomic Mass: \(hydrogenAtomicMassString)"
}
}
I am getting the error at:
let hydrogenAtomicNumberString = String("\(hydrogenAtomicNumberPassed!)")
I'm assuming it would happen to this line also if I fix only that line:
let hydrogenAtomicMassString = String("\(hydrogenAtomicMassPassed!)")
I have this code in my "searchViewController" (the .swift file used for the table view):
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("row selected : \(indexPath.row)")
if indexPath.row == 0 {
let hydrogenSearchSegue = UIStoryboard(name:"Main",
bundle:nil).instantiateViewController(withIdentifier: "hydrogenView") as!
hydrogenViewController
self.navigationController?.pushViewController(hydrogenSearchSegue,
animated:true)
}
}
When I click on the "Hydrogen" cell in the table view it crashes to this error:
Hydrogen cell
The crash
When I click on the "H" button in this image it will take me to the hydrogen view controller:
Image of the Hydrogen Button in the simulator (Top Left)
Image of the Hydrogen View Controller
I want the hydrogen cell to segue over to the hydrogen view controller just like the button can.
When this same issue came up earlier I just had an issue with the name of the segue in the storyboard. However, because there is no visible segue from the table view, I don't know how to fix the issue.
I've tried this:
performSegue(withIdentifier: "hydrogenSegue", sender: nil)
I was thinking that I could just reuse the "hydrogenSegue" from the button to the view controller but I get a SIGABRT error. It just says that there is no segue with the name "hydrogenSegue." It would be best if I could just reuse that segue in a way because everything is already connected but I now found out that the "searchViewController" can't recognize the segue. Any help is appreciated and my main goal is to just get the cell that is clicked on to move over to the element's designated view. I tried to provide as much information as possible without making it to long and if there is any more information needed, I should be able to provide it.
well. first answer
in your hydrogenViewController try with this lines.
var hydrogenAtomicNumberPassed: Int?
var hydrogenAtomicMassPassed: Float?
override func viewDidLoad(){
super.viewDidLoad()
self.viewBackgroundColor = .gray
}
override func viewWillAppear(){
super.viewWillAppear()
if let number = hydrogenAtomicNumberPassed
{
hydrogenAtomicNumberLabel.text = "Atomic Number: \(number)"
}
if let mass = hydrogenAtomicMassPassed
{
hydrogenAtomicMassLabel.text = "Atomic Mass: \(mass)"
}
}
Now, the segues only "lives" between a couple viewControllers, if you have a third view controller, the last will not recognize him.
other thing, you are using segues and navigation controller, from my point of view, it's a bad idea mixed both, I mean, there are specific apps that can use both ways to present views, only is a advice.
if you want to pass data with pushviewcontroller only use this line
if indexPath.row == 0 {
let hydrogenSearchSegue = UIStoryboard(name:"Main",bundle:nil).instantiateViewController(withIdentifier: "hydrogenView") as! hydrogenViewController
hydrogenSearchSegue.VAR_hydrogenViewController = YOURVAR_INYOURFILE
self.navigationController?.pushViewController(hydrogenSearchSegue, animated:true)
}
tell me if you have doubts, and I will try to help you.

Swift: Cannot change properties in instantiated view controller [duplicate]

This question already has answers here:
What does "Fatal error: Unexpectedly found nil while unwrapping an Optional value" mean?
(16 answers)
Closed 4 years ago.
I am totally new at Swift so what follows may be a very basic question. Plus it's my first one at stackoverflow.com!
I am getting the "Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value" while setting text properties of UILabels in an instantiated view controller.
I am making a simple app to show a list of medicines i have at home and their expiry dates. A table view controller shows a list of the medicines, from an array called medArray, and when each one is tapped I want it to load a detail screen with some further details.
I set up a DetailView controller using IB and created the outlet connections of 3 UILabels:
// DetailView.swift
// HomeMed
//
// Created by Joao Boavida on 10/12/2018.
// Copyright © 2018 Joao Boavida. All rights reserved.
//
import UIKit
class DetailView: UIViewController {
#IBOutlet var nameLbl: UILabel!
#IBOutlet var subtextLbl: UILabel!
#IBOutlet var expdateLbl: UILabel!
}
I think the connections are successfully made as the dots on the left gutter are solid. I do get the error when tapping on a table view cell, which calls the following code:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let medicine = medArray[indexPath.row]
if let vc = storyboard?.instantiateViewController(withIdentifier: "DetailView") as? DetailView {
vc.nameLbl.text = medicine.name
if let subtext = medicine.subtext {
vc.subtextLbl.text = subtext
} else {
vc.subtextLbl.text = ""
}
let ISOdateFormatter = ISO8601DateFormatter()
ISOdateFormatter.formatOptions = [.withMonth, .withYear, .withDashSeparatorInDate]
vc.expdateLbl.text = "Exp: \(ISOdateFormatter.string(from: medicine.expiry))"
navigationController?.pushViewController(vc, animated: true)
}
}
I get the error as soon as I try to access vc.nameLbl.text, which is nil for reasons I don't know. vc.expdateLbl and vc.subtextLbl are also nil.
The "vc" view controller seems to be instantiated correctly because if I omit the configuration code and push it just after creating it it displays fine, albeit with the initial text in the labels that I set in IB. It is only when I try to change those labels that it crashes.
I have tried making and remaking the outlet connections with no success. Can anyone help troubleshooting this?
Thank you very much!
You are setting your VC's outlets here, right?
vc.nameLbl.text = medicine.name <---
if let subtext = medicine.subtext {
vc.subtextLbl.text = subtext <---
} else {
vc.subtextLbl.text = "" <---
}
...
vc.expdateLbl.text = "Exp: \(ISOdateFormatter.string(from: medicine.expiry))" <---
The outlets nameLbl, subtextLbl and expdateLbl are all nil when the above code is run. They are not set at that point in time yet.
The outlets are already set when viewDidLoad is called. Therefore, what you can do is to add three properties in your DetailView:
var name: String!
var subtext: String!
var expdate: String!
Instead of setting the outlets, you set the above three properties:
vc.name = medicine.name <---
if let subtext = medicine.subtext {
vc.subtext = subtext <---
} else {
vc.subtext = "" <---
}
...
vc.expdat = "Exp: \(ISOdateFormatter.string(from: medicine.expiry))" <---
Then, in the DetailView, assign the properties to the text of the labels in viewDidLoad:
override func viewDidLoad() {
nameLbl.text = name
subtextLbl.text = subtext
expdateLbl.text = expdate
}
It crashes because your are trying to access a UILabel before it is loaded. So, you should call vc.loadViewIfNeeded(). This will load all the UIViewController views.

iOS - How to init and insert UICollectionViewCell programmatically when data is ready?

I would like to add Ads in my App, like Native Ads or Parallax.
The App is mainly an UICollectionView with many cells displaying different information.
I am using an SDK in order to load the Ad Object.
I have made a custom UICollectionViewCell class in order to init the cell and load the Native Ad as a cell in my UICollectionView.
#objc class AdsCollectionViewCell: BaseCollectionViewCell, AdapterAdsDelegate {
#IBOutlet var outletNativeTitle: UILabel!
#IBOutlet var outletImageNative: UIImageView!
#IBOutlet var outletButtonAction: UIButton!
#IBOutlet var clickableView: UIView!
#IBOutlet var mediaContainer: UIView!
#IBOutlet var bottomView: UIView!
var adFactory = AdsSDKFactory()
var adObject = AdsObject()
#objc func initCell(viewController: HomeViewController) {
DispatchQueue.main.async {
self.isHidden = true
}
// Set some properties for the Ads SDK
adFactory.nativeDelegate = self
adObject.viewController = viewController
adFactory.placementId = "154569"
// method from the SDK to request the Ad
adFactory.loadAds()
}
It is working but my main issue is that I am displaying an empty cell and the content is displayed when I got the delegate response method from the Ad SDK with the Ad Object.
Here is the delegate method I receive in my AdsCollectionViewCell.
func adObjectDidLoad(adView: AdsObject!) {
self.adObject = adView
self.updateCellOutlets() // my method to update labels from the adView
self.setNeedsDisplay()
self.delegate.tileCellDidSucceed!(self)
}
I would like to first init and make the call to the adFactory.loadAds in my HomeViewController, so that when I got the delegate response and the adObject, I don't updateCellOutlets directly but I can self.delegate.tileCellDidSucceed!(self) which is the delegate from my BaseCollectionViewCell in order to notify my HomeViewController that the cell is ready.
How can init a cell outside of the cellForRow method to dynamically insert it when the Ad is loaded ?
EDIT: I have followed the advices and create a Singleton AdsLoader class, that set all the parameters to request the ad, and also make the load request.
I set my HomeViewController as delegate so that I can get the view ad as response directly in my HomeViewController.
My issue now is to insertObject in the collectionView at the right indexPath. Which object should I insert?
I am unable to get a clear picture of the code and specific requirements of your AdSDK. So i will go ahead and write a pseudo code as per my understanding of what needs to be done. Here is what I purpose, instead of calling adFactory.loadAds() into the UICollectionViewCell object, you can perform this task into a different class object. Let say AdLoader
So your add loader should be something like this.
class Adloader {
var adFactory = AdsSDKFactory()
typealias adLoadCompletionHandler = (adView: AdsObject) -> Void
var loadCompletion
func initLoader() {
//Set some properties for the Ads SDK
adFactory.nativeDelegate = self
// method from the SDK to request the Ad
adFactory.loadAds()
}
}
Once the ad is loaded, you can use a completion handler to get update the collection view.
func adObjectDidLoad(adView: AdsObject!) {
adLoadCompletionHandler(adView)
}
So your code can be something like this.
if (needToInsertAd) {
var newAd = Adloader.initLoader()
newAd.adLoadCompletionHandler {
//Code to insert cell at indexpath
}
}
Hope this helps you.

Global Variables vs Direct Conection to pass data between view controllers ios

I don't think the title uses the right terminology so I'll try to clarify now.
I have two view controllers which i want to pass data between. view 1 has a tableView and the 2nd view has a MKMapView. Now in the corresponding controllers I want when you click on a cell in view 1 it sends you to the place on the map which the cell indicates eg. a New York cell would send to a map of new York. So I tried in the didSelectRowAtIndexPath that I create an instance of the second controller which would transfer the data to it. But when I did that it would always return a nil value in the second view controller.
So instead I created a 3rd swift file which has some global variables and when I transfer the data via them it works perfectly. Why is this so?
Cheers.
EDIT: Before the I went via a third file
Code for the ViewController 1
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var selectedCell = array[indexPath.row]
var viewController2 = ViewController2()
viewController2.textLabel1 = selectedCell.name
coord.textLabel2 = selectedCell.coordinate
}
Code for ViewController 2
#IBOutlet weak var textLabel1: UILabel!
#IBOutlet weak var textLabel2: UILabel!
Code for View Controller 2 going via 3rd file
#IBOutlet weak var textLabel1: UILabel!
#IBOutlet weak var textLabel2: UILabel!
override func viewDidLoad() {
textLabel1.text = globalVar1
textLabel2.text = globalVar2
}
Code from the 3rd File
var globalVar1: String!
var globalVar2: String!
So from the comments below I take it that in the first way the textLabels hadn't been initialised yet, so the values I assigned to them where turned into nil values. Is this correct? If so how would you do the first way correctly
If I had to guess, and I would because I cannot comment for more info yet(i get in trouble ).
It's because you are trying to assign it to an outlet.
The outlet has not been set yet which means when it is set (I think around ViewDidLoad)
the outlet will be set to nil.
The properties should however be retained if the object hasn't gone out of the heap that is.
PSedu code:
first view :
Your table view
Second View:
your map view
How to work
In you second view:
Add a NSMutableArray *valueArraytoGet in .h file,set its property
#synchronize it in .m file
Now in your first view at didSelectRowAtIndex method
create object of Second View Controller
and assign data as
SecondViewController *object=......
object.valueArraytoGet=[assign ur value array here]....
Hope it will help

Resources