How to call a function from another controller in swift - swift

I set the Show Charts button on the DetailView Controller which triggers the getChartData function and shows me the values in display view in charts, now I want to call that function in the didselectrow on the main Viewcontroller so that the chart is loaded automatically, but it fails.
When I tried to call that function in didselectrow (DVC.getChartsData) I got the error "Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value"
DVC.getChartsData
Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
ViewController:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let Storyboard = UIStoryboard(name: "Main", bundle: nil)
let DVC = Storyboard.instantiateViewController(withIdentifier: "DetailViewController") as! DetailViewController
DVC.getDetailName = coin[indexPath.row].name
let formatedRoundingPrice = (coin[indexPath.row].price as NSString).floatValue * currencymodel.indexValue
let formatedPrice = String (format: "%.3f", formatedRoundingPrice)
DVC.getDetailPrice = formatedPrice
self.navigationController?.pushViewController(DVC, animated: true)
let percentage = String ((coin[indexPath.row].percent as NSString).floatValue)
DVC.getDetailPercent = percentage
tableView.deselectRow(at: indexPath, animated: true)
//DVC.getChartData()
}
DetailViewController:
#IBAction func tapLineChart(_ sender: Any) {
getChartData()
}
func getChartData () {
let chart = HITLineChartView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: displayView.bounds.height))
displayView.addSubview(chart)
let max = String((priceResult.max() ?? 0.0).rounded(.up))
let min = String((priceResult.min() ?? 0.0).rounded(.down))
let maxChange = abs((listOfChanges.max()) ?? 0.0).rounded(.up)
let minChange = abs((listOfChanges.min()) ?? 0.0).rounded(.up)
absMaxPercentage = Int(maxChange > minChange ? maxChange : minChange)
titles = ["\(getDetailName) closing price is \(getDetailPrice)"]
print(data)
chart.draw(absMaxPercentage,
values: listOfChanges,
label: (max: max, center: "", min: min),
dates: namesArray,
titles: titles)
addCloseEvent(chart)
finalURL = baseURL + "bitcoin" + "/market_chart?vs_currency=usd&days=5"
print(finalURL)
getBitcoinData(url: finalURL)
}
How to load my charts tap on a specific tableview cell instead of tapping on tapLineChart.
https://imgur.com/fg2502P
https://imgur.com/C4AzaRY
https://imgur.com/jOrwujy

if you want to call a function on viewControllerB that you declare from viewController A.
just create the object of the class file you want to use the function from
var obj mainVC = MainViewController()
class MainViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
func commonMethod() {
print("From the main class")
}
}
Using that object, call the function in another file where you mean to use it
class OtherViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
mainVC.commonMethod()
// Do any additional setup after loading the view, typically from a nib.
}
}
Additionally, You can also create a new swift file, name it Global.swift, create all your functions that you want to use throughout the application here. They become "global functions"

You will want to use delegates or observers to pass data between view controllers.
I'm new to tutorials, but I wrote a bit about this here: https://www.eankrenzin.com/swift-blog/pass-data-throughout-your-app-with-observers-and-notifications-xcode-11-amp-swift-5
You should use optional binding to unwrap your VC let DVC = Storyboard.instantiateViewController(withIdentifier: "DetailViewController") as! DetailViewController
Your code is crashing because of that line. Check your interface builder to make sure the identifier is correct. Edit: this line was not causing a crash, but it is still better to use optional binding.The line is: https://imgur.com/CVP1x6H
NOTE: It is terrible practice to litter your app with instances when delegates and observers could work. Also do NOT have globals. Globals are disastrous for debugging and create tech debt.

Related

Issue passing data to iOS UIViewController

Im trying to pass an enum to a UIViewController in another storyboard but the error is that UIViewController has no member ViewType. When I checked the variable type after forcing ! it is still a UIViewController not a QuotesTestmoniesViewController
How come the type of the variable is not being changed to the new view controller type? What am I doing wrong here?
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
var viewController = UIViewController()
switch indexPath.item {
//Saints
case 0:
viewController = self.amiStoryboard.instantiateViewController(withIdentifier: "militarysaintscontroller") as! SaintsViewController
self.navigationController?.pushViewController(viewController, animated: true)
//Prayers
case 1:
break;
//Testmonies
case 2:
break;
//Quotes
case 3:
viewController = self.amiStoryboard.instantiateViewController(withIdentifier: "militaryquotestestmoniescontroller") as! QuotesTestmoniesViewController
viewController.viewType = ViewType.Quotes //ERROR
self.navigationController?.pushViewController(viewController, animated: true)
default:
return
}
}
Looks like you are trying to use a single line for pushing the different view controller.
But in code your are also written push line for all cases.
It will not work by casting the value and assign. You have to cast the controller at the time of passing data. Like this
(viewController as? QuotesTestmoniesViewController).viewType = ViewType.Quotes
You can make a generic function for pushing view controller like this
func pushNextViewController<T: UIViewController>(viewController: T) {
self.navigationController?.pushViewController(viewController, animated: true)
}
Usage :
let viewController = self.amiStoryboard.instantiateViewController(withIdentifier: "militaryquotestestmoniescontroller") as! QuotesTestmoniesViewController
viewController.viewType = ViewType.Quotes
pushNextViewController(viewController: viewController)
//----
let viewController2 = self.amiStoryboard.instantiateViewController(withIdentifier: "militarysaintscontroller") as! SaintsViewController
pushNextViewController(viewController: viewController2)
A variable of type T can hold values of type T. Doesn’t matter if you do something like assign it using an expression in which you cast a T to a U. Still a variable of type T.

Unable to share data between tab view controllers?

I am making an app where I take in user input and display it as a chart, which requires an array of data. I have managed to save data in an array using core data and I cannot figure out how to share that data from one tab to the other TabViewController.
here is how the data is stored and fetched in the FirstViewController
let number = Numbers(context: PersistenceService.context)
number.numberInArray = Int16(numberEnteredInSlider)
PersistenceService.saveContext()
testArray.append(Int(Double(number.numberInArray)))
var numbers = [Numbers]() // Where Numbers = NSManaged Class
var fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Numbers")
do {try numbers = PersistenceService.context.fetch(fetchRequest) as! [Numbers]
for number in numbers {
print(number.numberInArray)
}
}catch {
print("error")
}
and here is the output(printed testarray):
SAVED
2
5
6
5
Now I want to share this test array from one view controller to another(chartsViewController)
this is what I have tried
class chartsViewController: UIViewController {
let mainVC = mainViewController(nibName: "mainViewController", bundle: nil)
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print(mainVC.testArray)
updateGraph()
func updateGraph() {
var lineChartEntry = [ChartDataEntry]() //this is the Array that will eventually be displayed on the graph.
for i in 0..<mainVC.testArray.count {
//
let value = ChartDataEntry(x: Double(i), y: Double(mainVC.testArray[i]))
// here we set the X and Y status in a data chart entry
lineChartEntry.append(value)
// here we add it to the data set
}}
//only showing the part needed. I have tried the same solution with another array and it worked.
}
and the output comes as [0]
I have also tried making a singleton but that didn't work out.
To pass data between tabs on UITabBarController /tabBar, what needs to be done is to have an intermediate. (This is usually the main UITabBarController)
Pic of UITabBarController and the child tabbar
Create a Class and link it to this TabBarController within IB
class BaseTBController: UITabBarController {
// Provide the variable which we want to pass
var workoutTitle: String = "Select a Workout"
override func viewDidLoad() {
super.viewDidLoad()
}
}
Assuming you want to pass data from TabBar2 to TabBar1, then on TabBar2 (in this case, I have it as a UITableView). In the delegate method:
extension VCLibrary: UITableViewDelegate{
// method to run when table view cell is tapped
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// prepare to store the data to be passed to another TabBar
let tabbar = tabBarController as! BaseTBController
tabbar.workoutTitle = jsonErgWorkouts[indexPath.row].title
// Automatically select Tab1 after choosing
self.tabBarController?.selectedIndex = 0
// Deselect the selected row once we move to Tab1
tableView.deselectRow(at: indexPath, animated: true)
}
}
After selecting the data to be passed, the code (above) will automatically switch to Tab1. Within Tab1, the following code is aimed to receive the passed data
override func viewDidAppear(_ animated: Bool) {
// Obtain Passed in values from BaseTBController
let tabbar = tabBarController as! BaseTBController
// populate the Title as passed from Tab2
workoutTitleLabel.text = tabbar.workoutTitle
}
I learned this from:
https://www.youtube.com/watch?v=GL8-eM93EvQ

unit testing cell is nil

Issue:
I loaded collectionView with 3 Dummy items. However Cell came back nil, is it because view was never loaded? How do you guys test your collectionViewCell type?
Code
var window: UIWindow?
var sut: QuestsDataProvider!
var collectionView: UICollectionView!
override func setUp() {
super.setUp()
bulletinController = BulletinController(collectionViewLayout: UICollectionViewFlowLayout())
sut = QuestsDataProvider(acceptedQuests: false, completedQuests: false)
bulletinController.collectionView?.dataSource = sut
bulletinController.collectionView?.delegate = sut
window = UIWindow()
window?.makeKeyAndVisible()
window?.rootViewController = bulletinController
}
func testCellIsQuestCell() {
let indexPath = IndexPath(item: 1, section: 0)
let cell = collectionView.cellForItem(at: indexPath)
guard let count = sut.questManager?.quests.count else {return XCTFail()}
XCTAssertTrue(cell is QuestCell)
}
Edit:
Upon Further testing, I'm able to see the dummy Cell inside my simulator and get a accurate count from numberOfitems(InSection: Int). However I have no visible Cell.
2nd Edit:
After further research, I found out the issue is collectionView.cellForItem(at: indexPath) only shows visible cell. Is there any other method for unit testing collection view cell type?
You need to access the view object of the view controller before it and its subview components will be fully initialised.
You should be able to just do let _ = bulletinController.view in your setup function. it is quite a common approach, see here
Relevant parts included below
func setupCreateOrderViewController()
{
let bundle = NSBundle(forClass: self.dynamicType)
let storyboard = UIStoryboard(name: "Main", bundle: bundle)
createOrderViewController = storyboard.instantiateViewControllerWithIdentifier("CreateOrderViewController") as! CreateOrderViewController
_ = createOrderViewController.view
}
Quote from link:
But there are two very, very important things happening on the last line:
Asking for the view property of createOrderViewController causes the view to be loaded. The viewDidLoad() method is called as a result.
After the view is loaded, all the IBOutlets are also set up and ready to be used in out tests. For example, you can assert that a text field outlet’s text equal to a string you expect.
EDIT:
You can also just call loadViewIfNeeded() on the view controller, which will do the same thing.
Loads the view controller’s view if it has not yet been loaded.

My cells are duplicating themselves

I am new to swift and I am trying to make this note app. I have split view controller that goes in my first view controller and that view controller connects to a table view controller. Everything works perfectly is just that when I launch the app I have all the notes like I want but when I try to go back to my first view controller and come back to my table view controller, all the notes are duplicated every single time I do it. I tried everything I can try, is there anyone who can help me
my MasterViewController is
import UIKit
class MasterViewController: UITableViewController {
var detailViewController: DetailViewController? = nil
override func viewDidLoad()
{
super.viewDidLoad()
Note.loadNotes() // The problem is here, I think
noteTable = self.tableView
// Do any additional setup after loading the view, typically from a nib.
let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(insertNewObject(_:)))
navigationItem.rightBarButtonItem = addButton
if let split = splitViewController
{
let controllers = split.viewControllers
detailViewController = (controllers[controllers.count-1] as! UINavigationController).topViewController as? DetailViewController
}
}
My loadNotes function is
class func loadNotes()
{
let defaults:UserDefaults = UserDefaults.standard
let saveData: [NSDictionary]? = defaults.object(forKey: kAllNotes) as? [NSDictionary]
if let data:[NSDictionary] = saveData
{
for i:Int in 0 ..< data.count
{
let n:Note = Note()
n.setValuesForKeys(data[i] as! [String : Any])
allNotes.append(n)
}
}
}
Your loadNotes method keeps appending. The first line of loadNotes should be:
allNotes = [Note]()
Then it starts with an empty array and fills it up.
And why is loadNotes a static method? That's a bad design. Make Notes a normal class and make loadNotes an instance method.
On an unrelated note (no pun intended), do not use UserDefaults to store app data. Only use it to store little bits of information.

Sending data to another view: can't unwrap option

I know that this has to be a simple fix, but can't seem to understand why my code is not working. Basically I am trying to send a value from a text field in 1 view to a 2nd view's label.
ViewController.swift
#IBOutlet var Text1st: UITextField
#IBAction func Goto2ndView(sender: AnyObject) {
let view2 = self.storyboard.instantiateViewControllerWithIdentifier("view2") as MyView2
//view2.Label2nd.text=text;
self.navigationController.pushViewController(view2, animated: true)
}
MyView2.swift
#IBOutlet var Label2nd: UILabel
override func viewDidLoad() {
super.viewDidLoad()
var VC = ViewController()
var string = (VC.Text1st.text) //it doesn't like this, I get a 'Can't unwrap Option.. error'
println(string)
}
-------EDITED UPDATED CODE FROM (drewag)-------
ViewController.swift
let text = "text"
var sendString = Text1st.text
println(sendString) //successfully print it out.
let view2 = self.storyboard.instantiateViewControllerWithIdentifier("view2") as MyView2
view2.Label2nd.text=sendString;
self.navigationController.pushViewController(view2, animated: true)
MyView2.swift
#IBOutlet var Label2nd: UILabel
override func viewDidLoad() {
super.viewDidLoad()
var VC = ViewController()
var string = self.Label2nd.text
println(string) //still getting the error of an unwrap optional.none
}
var VC = ViewController() creates a new instance of ViewController. Unless there is a default value, you are not going to get any value out of VC.Text1st.text. You really should use a string variable on your second view controller to pass the data to it.
Also, a note on common formatting:
Class names should start with a capital letter (as you have)
Method / function names should start with a lower case letter
UIViewController subclasses should have "Controller" included in their name, otherwise, it looks like it is a subclass of UIView which is an entirely different level of Model View Controller (the architecture of all UIKit and Cocoa frameworks)
Edit:
Here is some example code:
class ViewController1 : UIViewController {
...
func goToSecondView() {
var viewController = ViewController2()
viewController.myString = "Some String"
self.navigationController.pushViewController(viewController, animated: true)
}
}
class ViewController2 : UIViewController {
var myString : String?
func methodToUseMyString() {
if let string = self.myString {
println(string)
}
}
...
}
Note, I am not creating ViewController2 using a storyboard. I personally prefer avoiding storyboards because they don't scale well and I find editing them to be very cumbersome. You can of course change it to create the view controller out of the storyboard if you prefer.
jatoben is correct that you want to use optional binding. IBOutlets are automatically optionals so you should check the textfield to see if it is nil.
if let textField = VC.Text1st {
println(textField.text)
}
This should prevent your app from crashing, but it will not print out anything because your text field has not yet been initialized.
Edit:
If you want to have a reference to your initial ViewController inside your second you're going to have to change a few things. First add a property on your second viewcontroller that will be for the first view controller:
#IBOutlet var Label2nd: UILabel //existing code
var firstVC: ViewController? //new
Then after you create view2, set it's firstVC as the ViewController you are currently in:
let view2 = self.storyboard.instantiateViewControllerWithIdentifier("view2") as MyView2 //already in your code
view2.firstVC = self //new
Finally in your viewDidLoad in your second view controller, use firstVC instead of the ViewController you recreated. It will look something like this:
override func viewDidLoad() {
super.viewDidLoad()
if let textField = firstVC?.Text2nd {
println(textField.text)
}
}
Use optional binding to unwrap the property:
if let string = VC.Text1st.text {
println(string)
}