Issue passing data to iOS UIViewController - swift

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.

Related

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

How to call a function from another controller in 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.

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.

Swift: Accessing current navigation controller from a UICollectionViewCell

I have a UICollectionViewCell class "ProductCell"; I am trying to access the current navigation controller in order to update a barbuttonicon. I have tried the following code as this is what I use in my other UIViewControllers:
let nav = self.navigationController as! MFNavigationController
nav.updateCartBadgeValue()
However it states that the
value of type ProductCell has no member navigationController
I am aware that this is not a UIViewController but surely you should be able to access the current navigation controller the same way?
I also know that you can access the navigation controller by using UIApplication in the following way:
let navigationController = application.windows[0].rootViewController as! UINavigationController
I am not sure if that is a good way of doing it though.
Any help is much appreciated
Thanks
UIResponder chain will help here.
You can search the responder chain to find the controller for any view
extension UIView {
func controller() -> UIViewController? {
if let nextViewControllerResponder = next as? UIViewController {
return nextViewControllerResponder
}
else if let nextViewResponder = next as? UIView {
return nextViewResponder.controller()
}
else {
return nil
}
}
func navigationController() -> UINavigationController? {
if let controller = controller() {
return controller.navigationController
}
else {
return nil
}
}
}
controller() will return the closest responder that is of type UIViewController
Then on the returned controller you just need to find its navigation controller. You can use navigationController() here.
The simplest way is to add a property to you cell class that weakly references a UINavigationController
weak var navigationController: UINavigationController?
you will need to assign it in your cellForRow(atIndexPath:_) method.
let cell = tableView.dequeueReusableCell(withIdentifier: "yourReuseID") as! YourCellClass
cell.navigationController = navigationController //will assign your viewController's navigation controller to the cell
return cell
Unless things change, this is a good way to do it. To give you an example of a messier solution... You could add a
let hostViewController:UIViewController
property to your cell and add an initializer to handle it
let cell = ProductCell(vc: self)
But I don't think that's a better way to do it. your suggestion works fine.
let navigationController = application.windows[0].rootViewController as! UINavigationController

Why do I need to force the type using this Swift generic function?

I had some repetitive UIViewController boiler-plate scattered around that I wanted to encapsulate, so I defined this generic UIViewController extension method:
extension UIViewController {
func instantiateChildViewController<T: UIViewController>(
storyboardName: String? = nil,
identifier: String? = nil
) -> T {
let storyboard: UIStoryboard!
if let name = storyboardName {
storyboard = UIStoryboard(name: name, bundle: nil)
}
else {
storyboard = UIStoryboard(name: "\(T.self)", bundle: nil)
}
let vc: T!
if let identifier = identifier {
vc = storyboard.instantiateViewController(withIdentifier: identifier) as! T
}
else {
vc = storyboard.instantiateInitialViewController()! as! T
}
self.addChildViewController(vc)
self.view.addSubview(vc.view)
return vc
}
}
However, when I use this extension like so:
class ChildViewController: UIViewController { /*...*/ }
class ParentViewController: UIViewController {
private var childVC: ChildViewController!
//...
func setupSomeStuff() {
self.childVC = self.instantiateChildViewController() //<-- Compiler error
let vc: ChildViewController = self.instantiateChildViewController() //<-- Compiles!
self.childVC = vc
}
}
I get the compiler error Cannot assign value of UIViewController to type ChildViewController! on the line with the comment above. However, if I use an intermediate variable that I explicitly give a type to it works.
Is this a Swift bug? (Xcode 8.1) My interpretation of how generics work is that in this case T should equal the more specific ChildViewController, not the less constrained UIViewController. I get the same issue if I defined childVC as private var childVC: ChildViewController?, the only work-around I've found is the local variable, which obviously makes the extension less compelling, or to do an explicit cast like:
self.childVC = self.instantiateChildViewController() as ChildViewController
I've seen this too. I think there's some weird behavior around Optionals the compiler isn't dealing with as expected.
If you change the return value of the function to an optional value it should work without a problem.
func instantiateChildViewController<T: UIViewController>(//whateverParams) -> T!
or
func instantiateChildViewController<T: UIViewController>(//whateverParams) -> T?
Also, your childVC should be a var rather than a let if you're going to set it anyplace other than an initializer