View Controller not loading via instantiateViewController function even with correct identifier - ios

Goal: In a separate storyboard that is loaded via a storyboard reference in the main.storyboard, in a pageViewController acting as the initial view controller, I want to initialize an array object of viewControllers via the function .instantiateViewController(identifier:).
Issue: The last viewController I'm trying to instantiate as a constant is not loading. The error - *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Could not load the scene view controller for identifier 'FinalVC''"
All other viewControllers in this storyboard load fine. This last view controller has a correct custom class linked and a unique storyboard identifier.
Debugging: I've created a breakpoint where this view controller is instantiated and noticed in the debugging console all other view controller objects load as "BillyCues.repeatViewController + unique identification number" while this last vc loads as "UIViewController + 0x000000000000000". It's almost as if this vc is not a part of the app bundle or referenced correctly but it's there when I search in the directory.
Debugging console screen
Things I've tried that did not work:
Check to see if another vc has the same identifier
Clean the build folder
Check "Use Storyboard ID" in the identity inspector
let finalVC = storyBoard.instantiateViewController(identifier: "FinalVC") as! FinalViewController
Restart Xcode
Create a brand new view controller with a different storyboard identifier using the same custom class
Removed all connections from buttons and labels in the last vc
Made sure all storyboard references in main.storyboard has the correct storyboard linked
Conclusion: All my googling has led to other developers encountering the error about NIBs or tableviews not necessarily a view controller. If my vc has a correct custom class and unique identifier the error should not occur. If anyone can offer guidance I'd appreciate it; I'm dumbfounded.
I hope I've asked for help in an appropriate structure but please let me know if more code or screenshots are needed.
PageViewController Code
import UIKit
class LauncherViewController: UIPageViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.setViewControllers([viewControllerList[0]], direction: .forward, animated: false, completion: nil)
// Do any additional setup after loading the view.
}
private var viewControllerList: [UIViewController] = {
let storyBoard = UIStoryboard.cueCreation
let firstVC = storyBoard.instantiateViewController(identifier: "CueNameVC")
let secondVC = storyBoard.instantiateViewController(identifier: "DueDateVC")
let thirdVC = storyBoard.instantiateViewController(identifier: "IconVC")
let fourthVC = storyBoard.instantiateViewController(identifier: "IconColorVC")
let fifthVC = storyBoard.instantiateViewController(identifier: "RepeatVC")
let finalVC = storyBoard.instantiateViewController(identifier: "FinalVC") as! FinalViewController
return [firstVC, secondVC, thirdVC, fourthVC, fifthVC, finalVC]
}()
var selectedReminderBill: CueObject?
public var currentIndex = 0
static var cueName: String = ""
static var cueDate: Date = Date()
static var cueIcon: Data = Data()
static var iconColor:String = "14CC7F"
static var repeatMonthly: Bool = false
// Navigation button functions below to move to the next or previous page
func pushNext() {
if currentIndex + 1 < viewControllerList.count {
self.setViewControllers([self.viewControllerList[self.currentIndex + 1]], direction: .forward, animated: true, completion: nil)
currentIndex += 1
}
}
func pullBack() {
print(currentIndex)
if currentIndex - 1 < viewControllerList.count {
self.setViewControllers([self.viewControllerList[self.currentIndex-1]], direction: .reverse, animated: true, completion: nil)
currentIndex -= 1
}
}
}
FinalViewController Code
import UIKit
import UserNotifications
import RealmSwift
class FinalViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
cueName.text = LauncherViewController.cueName
dueDate.text = CueLogic.convertPaymentDateToString(for: LauncherViewController.cueDate)
iconBackgroundView.backgroundColor = colorLogic.colorWithHexString(hexString: LauncherViewController.iconColor)
cueIcon.image = UIImage(data: LauncherViewController.cueIcon)
repeatsMonthly.text = repeatMonthlyToString
}
override func viewDidLoad() {
super.viewDidLoad()
cueName.layer.cornerRadius = 15
cueName.clipsToBounds = true
iconBackgroundView.layer.cornerRadius = 20
iconBackgroundView.clipsToBounds = true
dueDate.layer.cornerRadius = 15
dueDate.clipsToBounds = true
repeatsMonthly.layer.cornerRadius = 15
repeatsMonthly.clipsToBounds = true
backButton.layer.cornerRadius = 15
backButton.clipsToBounds = true
saveButton.layer.cornerRadius = 15
saveButton.clipsToBounds = true
// Do any additional setup after loading the view.
}
let colorLogic = ColorLogic()
let realm = try! Realm()
weak var delegate: HomeScreenDelegate?
var launcher = LauncherViewController()
var repeatMonthlyToString: String {
get {
if LauncherViewController.repeatMonthly == true {
return "Repeats Monthly: Yes"
} else {
return "Repeats Monthly: No"
}
}
}
#IBOutlet var cueName: UILabel!
#IBOutlet var dueDate: UILabel!
#IBOutlet var saveButton: UIButton!
#IBOutlet var backButton: UIButton!
#IBOutlet var iconBackgroundView: UIView!
#IBOutlet var cueIcon: UIImageView!
#IBOutlet var repeatsMonthly: UILabel!
#IBAction func dismissButtonTapped(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
#IBAction func backButtonTapped(_ sender: Any) {
if let pageController = parent as? LauncherViewController {
pageController.pullBack()
}
}
#IBAction func saveButtonTapped(_ sender: Any) {
// Request authorization from the user to allow notifications
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound], completionHandler: {success, error in
if success {
// schedule test
} else if let error = error {
print("error occured \(error)")
}
})
let newCue = CueObject()
let launcherVC = LauncherViewController.self
newCue.name = launcherVC.cueName
newCue.paymentDate = launcherVC.cueDate
newCue.icon = launcherVC.cueIcon
newCue.iconColor = launcherVC.iconColor
newCue.repeatsMonthly = launcherVC.repeatMonthly
NotificationLogic.scheduleLocalAlertForBill(named: newCue.name, due: newCue.paymentDate, repeatsMonthly: newCue.repeatsMonthly)
saveToDB(for: newCue)
delegate?.loadCuesFromRealm()
self.dismiss(animated: true, completion: nil)
}
func saveToDB(for cue: CueObject) {
do {
try realm.write({
realm.add(cue)
})
} catch {
print("Error - \(error)")
}
}
}
protocol HomeScreenDelegate: AnyObject {
func loadCuesFromRealm()
}
Extension I wrote in another viewController
extension UIStoryboard {
static let onboarding = UIStoryboard(name: "Onboarding", bundle: nil)
static let main = UIStoryboard(name: "Main", bundle: nil)
static let cueCreation = UIStoryboard(name:"CueCreation", bundle: nil)
}
Identity Inspector
Main Storyboard References

I'd do a few things as part of cleanup to start debugging the actual issue. In the storyboard extension, I'd rather use a static function to reference the view controller.
extension UIStoryboard {
class func createFinalVC() -> FinalViewController? {
return UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "FinalVC") as? FinalViewController)
}
}
And for implementing it, I'd use in the view controller presenting FinalViewController:
private func createCreateFinalVC() -> FinalViewController? {
return UIStoryboard.createFinalVC()
}
And finally pushing it into the view,
if let finalVC = createCreateFinalVC() {
yourNavController?.pushViewController(finalVC, animated: true)
}

Solution
I began a process of elimination and started to comment out all of the code in my FinalVC class. I learned that this line of code var launcher = LauncherViewController() was triggering the crash.
Given my limited beginner knowledge I don't know why this would cause a crash; I can only assume that Xcode was trying to initialize two LauncherViewControllers with identical identifier numbers or something along those lines.

Related

View controller's data changes on 2nd attempt

Hey my code changes data in another view controller on 2nd attempt on 1st just showing default values
Code inside button
#IBAction func check(_ sender: Any) {
makeRequest()
let fin = UIStoryboard(name: "FinalViewController", bundle: nil)
let pop = fin.instantiateInitialViewController()! as! FinalViewController
pop.img = icon
pop.state = state
pop.fail = failed
self.present(pop, animated: true)
}
Code inside 2nd view controller
class FinalViewController: UIViewController {
#IBOutlet weak var weathure_icon: UIImageView!
#IBOutlet weak var status: UILabel!
var fail = false
var img = ""
var state = ""
override func viewDidLoad() {
if fail == false{
super.viewDidLoad()
status.text = state
weathure_icon.image = UIImage(named: img+".png")
}
}
Please check three things in your code inside the button action.
Please cross-check, is makeRequest() asynchronous request??
is icon, state and failed parameters request coming from makeRequest() method, in this case you need to handle request data on main thread.
Please replace following in your code:
This
let fin = UIStoryboard(name: "FinalViewController", bundle: nil)
let pop = fin.instantiateInitialViewController()! as! FinalViewController
With
let fin = UIStoryboard(name: "STORYBOARD_NAME", bundle: nil)
let pop = fin.instantiateInitialViewController(identifier: "FinalViewController")! as! FinalViewController
Hope this will help you and make success with your implementation :)

UnitTesting ViewController that contains Eureka form

I'm trying to implement unit testing for one of my ViewControllers that contains a massive form generated by Eureka forms for Swift.
The code compiled well, but received two errors when test was executed.
Undefined symbol: nominal type descriptor for Eureka.BaseRow
Undefined symbol: Eureka.Form.allRows.getter : [Eureka.BaseRow]
The code in my test file
import XCTest
#testable import MyProject
class DataEntryViewControllerTest: XCTestCase {
var mainvc: MyProject.DataEntryViewController!
private func setupViewControllers() {
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
let newObject = ModelManager.createObject()
self.mainvc = storyboard.instantiateViewController(withIdentifier: "dataEntryView") as? DataEntryViewController
// this .dataEdit is required
self.mainvc.dataEdit = newObject
self.mainvc.loadView()
self.mainvc.viewDidLoad()
}
override func setUp() {
super.setUp()
self.setupViewControllers()
}
override func tearDown() {
mainvc = nil
super.tearDown()
}
func testViewDidLoad() throws {
XCTAssertNotNil(self.mainvc, "Main VC is nil")
let form = mainvc.form
// If i comment away both of these lines, the test would pass.
// having Either one of them kills the process
XCTAssertEqual(form.allRows.first?.tag, "")
XCTAssertEqual(mainvc.form.rowBy(tag: "date")?.baseValue as Date!, Date())
}
func testPerformanceExample() throws {
// This is an example of a performance test case.
self.measure {
// Put the code you want to measure the time of here.
}
}
}
Relavent code from the view controller
import UIKit
import Eureka
import CoreData
class DataEntryViewController: FormViewController, UITextFieldDelegate {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
// compulsory
var dataEdit: object!
#IBOutlet weak var addOrEditButton: UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
let form = formPrinter()
form.delegate = self
// Triggers hide or show form, incase the object is coming in already locked.
hideOrShowAllForms()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
}
//MARK: - Form printer
func formPrinter() -> Form {
form +++ Section("Date (UTC)")
<<< DateRow("date"){ row in
row.disabled = Condition(booleanLiteral: self.dataEdit?.isLocked ?? false)
} .cellSetup { cell, row in
row.title = "Date"
row.value = self.dataEdit == nil ? Date() : self.dataEdit?.date
row.dateFormatter?.timeZone = TimeZone(secondsFromGMT: 0)
cell.datePicker.timeZone = TimeZone(secondsFromGMT: 0)
}
return form
}
}
The solution was to load the VC in a UIWindow
var vc: Simply_Log_Beta.DataEntryViewController!
let window = UIWindow(frame: UIScreen.main.bounds)
private func setupViewControllers(isFlight: Bool = true) {
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
self.vc = storyboard.instantiateViewController(withIdentifier: "dataEntryView") as? DataEntryViewController
window.rootViewController = vc
window.makeKeyAndVisible()
}

nil Delegate between two ViewController with two different Bundle (swift)

nil Delegate between two ViewController with two different Bundle using swift 4 (commented in second code)
here is my code :
First ViewController :
class FirstVC : UIViewController, MerchantResultObserver{
var secVC = SecondVC()
override func viewDidLoad() {
secVC.delegate = self
let storyboard = UIStoryboard(name: “SecondVC”, bundle: Bundle(identifier: “SecondBundle”))
let controller = storyboard.instantiateInitialViewController()
self.present(controller!, animated: true, completion: nil)
secVC.initSecondVC(data)
}
func Error(data: String) {
print("-------------Error Returned------------- \(data)")
}
func Response(data: String) {
print("-------------Response Returned------------- \(data)")
}
}
Second ViewController :
public class SecondVC: UIViewController {
public weak var delegate: MerchantResultObserver!
public func initSecondVC(_ data : String){
print(data)
}
#IBAction func sendRequest(_ sender: UIButton) {
delegate?.Response(data: “dataReturnedSuccessfully”) // delegate is nil //
dismiss(animated: true, completion: nil) // returned to FirstVC without returning “dataReturnedSuccessfully” //
}
}
public protocol MerchantResultObserver: class{
func Response(data : String)
func Error(data : String)
}
Any help would be appreciated
var secVC = SecondVC()
and
let storyboard = UIStoryboard(name: “SecondVC”, bundle: Bundle(identifier: “SecondBundle”))
let controller = storyboard.instantiateInitialViewController() as? SecondVC
These both are different instances.
You can assign a delegate to the controller, like
controller.delegate = self
It will call the implemented delegate methods in First View Controller.
Full Code.
let storyboard = UIStoryboard(name: “SecondVC”, bundle: Bundle(identifier: “SecondBundle”))
if let controller = storyboard.instantiateInitialViewController() as? SecondVC {
//Assign Delegate
controller.delegate = self
//It's not init, but an assignment only, as per your code.
controller.initSecondVC(data)
self.present(controller, animated: true, completion: nil)
}
One more thing, Don't present View in ViewDidLoad. You can put a code in some button or in a delay method.

Passing a String/Object value to another ViewController

I am opening another ViewController using this:
let mainStoryboard: UIStoryboard = UIStoryboard(name:"Main", bundle:nil)
let homeViewController: UIViewController = mainStoryboard.instantiateViewController(withIdentifier: "IssueViewController")
self.present(homeViewController, animated: true, completion: nil)
Along with this, I need to pass a Person object and a String value to the 2nd ViewController.
struct Person {
var Name: String
var Details: String
}
What changes do I need to do to attach a Person object to my existing code?
EDIT: This is the 2nd ViewController
I am trying to retrieve the values from this view
class IssueViewController: UIViewController {
var person: Person = Person();
override func viewDidLoad() {
super.viewDidLoad()
}
}
//changes in first controller
let mainStoryboard: UIStoryboard = UIStoryboard(name:"Main",bundle:Bundle.main)
let homeViewController: IssueViewController = mainStoryboard.instantiateViewController(withIdentifier: "IssueViewController") as! IssueViewController
homeViewController.person = Person(Name:"ABC",Details:"XYZ")
homeViewController.bindWithData(yourStringObject)
self.present(homeViewController, animated: true, completion: nil)
//changes in second view controller
class IssueViewController: UIViewController {
var person: Person = Person(Name:"",Details:"");
override func viewDidLoad() {
super.viewDidLoad()
print(person.Name)
print(person.Details)
}
func bindWithData(yourStringObject:String){
//your code here.
}
}

Change the screen in swift

Following is the storyboard of my app:
The app establishes a connection with the server on screen1 and all the communication with the server is performed in the code of this screen only. We make a request on screen4 and send it to server through the code of screen1 and app receives the response from the sever. If app gets successful response then app should show screen5, where I get error.
I wrote different lines of code but failed. Following are line of code which I am using now:
import UIKit
class logoViewController: UIViewController {
#IBOutlet weak var act: UIActivityIndicatorView!
override func viewDidLoad() {
super.viewDidLoad()
self.act.startAnimating()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// syncreq function is called from the connectionViewController.
// connectionViewController is the common class for connecting to the remote server
func syncreq (JSONdata: AnyObject) { // Proceesing for PRMS response
// Getting the value from the JSON
var Successful = self.getIntFromJSON(JSONdata as NSDictionary, key: "Successful")
println("Value of Successful : \(Successful)")
if (Successful == 0){
//Method1 not worked
// let adduser = regVC()
// self.presentViewController(adducer, animated: true, completion: nil)
//Method2 not worked
//let adducer = self.storyboard?.instantiateViewControllerWithIdentifier("registrationID") as regVC
//self.navigationController?.pushViewController(adducer, animated: true)
//Method3 not worked
//let secondViewController = self.storyboard?.instantiateViewControllerWithIdentifier("registrationID") as regVC
//self.navigationController?.pushViewController(secondViewController, animated: true)
//performSegueWithIdentifier("registrationID", sender: self)
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
var setViewController = mainStoryboard.instantiateViewControllerWithIdentifier("registrationID") as RegisterViewController
self.presentViewController(setViewController, animated: false, completion: nil)
}
else if (Successful == 1){
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
var setViewController = mainStoryboard.instantiateViewControllerWithIdentifier("mnuID") as menuViewController
self.presentViewController(setViewController, animated: false, completion: nil)
}
}
func getIntFromJSON(data: NSDictionary, key: String) -> Int {
let info : AnyObject? = data[key]
// println("Value of data[key] : \(key)")
if let info = data[key] as? Int {
println("Value of value for \(key) : \(info)")
return info
}
else {
return 0
}
}
}
I got the following error:
Warning: Attempt to present <project.screen5: 0x7a094790> on <project.ViewController: 0x79639d90> whose view is not in the window hierarchy!
screenshot of error:
Your problem is that view of the ViewController 1 is not in the window hierarchy, thus ViewController 1 cannot present modal VC.
Cleanest fix would be to change your app architecture design - having one view controller perform all the network requests may cause more complications than just this one.
However, for 'just make it work' solution you can present modal VC from navigation controller, i.e.
self.navigationController?.presentViewController(setViewController, animated: false, completion: nil)

Resources