Xcode Moving from Storyboard - swift

I have a Xcode project using storyboards.
I have the typical setup,
Segue in storyboard
Call self.perform("..."....) from view controller
This then calls 'prepare(for segue: UIStoryboardSegue, sender: Any?)' allowing me to write variables in the new view controller before 'viewdidload' is called
Now the project is growing making using storyboards impractical.
We are taking a code approach loading views from code. The issue we are facing is how to pass data to new view before 'viewdidload' is called or what's the correct process to use.
We are loading view using
let nib = UINib(nibName: "TestView", bundle: nil)
let view = nib.instantiate(withOwner: self, options: nil).first as! TestViewController
view.delegate = self
view.dataModel = TestViewDataModel()
present(view, animated: true, completion: nil)
Issue we have is 'instantiate' calls viewdidload but ideally it needs data from 'datamodel'
Thanks

Add a new initializer to TestViewController that takes accepts whatever data you need as parameters.
From within it, call super.init(nibName: "TestView", bundle: nil). UIViewController's initializer will take care of finding the nib, instantiating itself from it, and settings itself as the owner.
class TestViewController: UIViewController {
let dataModel: TestViewDataModel
init(dataModel: TestViewDataModel) {
super.init(nibName: "TestView", bundle: nil)
self.dataModel = dataModel
}
//...
}

Related

How storyboards works under the hood?

i wonder how storyboards and class are connected.
I deleted all "main" mentions in target/project and info.plist. and set rootViewController in SceneDelegate:
guard let windowScene = (scene as? UIWindowScene) else { return }
window = UIWindow(windowScene: windowScene)
window?.rootViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Dad")
window?.makeKeyAndVisible()
my storyBoard:
ViewController:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .brown
}
}
what I get:
Question is: If I initializing Root ViewController from Storyboard in SceneDelegate. How it knows that background should be brown? I know that I picked ViewController as a Custom Class in Identity Inspector, but How it works under the hood?
like when wrote some code in
class ViewController
it goes into nib file too? How this interaction between storyboard and class works?
p.s: sorry for my english
it goes into nib file too?
No. The nib file only has what you put in the storyboard.
I know that I picked ViewController as a Custom Class in Identity Inspector, but How it works under the hood?
When you did this in your code:
UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Dad")
the nib is read, and iOS now sees that the VC with the identifier "Dad" has the class ViewController, so it makes an instance of that class by calling the init(coder:) initialiser, so that the things stored in the nib can be decoded, followed by a bunch of other lifecycle methods including but not limited to awakeFromNib, viewDidLoad, viewWillAppear, viewDidAppear, etc.
Since viewDidLoad of your class is called at some point, the background is set to brown.

using pushViewController in containerView Not work

I have created containerView which contain tableViewController.
The main issue, when user click on information, tableViewController must show the information which user clicked.
it show the information when I use present(<#T##UIViewController#>, animated: <#T##Bool#>, completion: <#T##(() -> Void)?#>)
but doesnt work if I call self.navigationController?.pushViewController
the main issue is when I make vc.info = info it doesnt work, the info has value but using injection the value in another class is nil.
Here is my code:
func showLoginDetailsOnIpad(encryptedDataBase: info) {
self.view.addSubview(loginContainer)
let mainStoryboard : UIStoryboard?
mainStoryboard = UIStoryboard(name: "StoryboardiPad", bundle: nil)
let vc = mainStoryboard!.instantiateViewController(withIdentifier: "tableVC") as! tableVC
vc.info = info
vc.showingLoginInfo = true
vc.modalPresentationStyle = .automatic
self.navigationController?.pushViewController(vc, animated: true)
}
Explanation
You mentioned the use of a containerView. The table controller being displayed inside the containerView, doesn't automatically get a reference to the nearest navigationController.
So in this line: self.navigationController?.pushViewController(vc, animated: true)
self.navigationController might actually be nil. You can confirm this by printing its value before running the push method like so:
print(self.navigationController) // See if this prints nil.
self.navigationController?.pushViewController(vc, animated: true)
Fixes
To fix the issue, I suggest to pass a reference to the parent controller's navigationController to the child controller - through the embed segue.
In your parent controller, you can use this code:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? UITableViewController{
destination.navigationController = self.navigationController
}
}
And in your table view controller, leave the code as is.

Using Storyboard and custom View Controller init

When pushing view controller programatically, one can easily do some dependency injection through the init method :
let dummyVC = DummyVC(dummyManager: DummyManager())
self.pushViewController(dummyVC, animated: true)
Using destination controller :
class DummyVC: UIViewController {
private let dummyManager: DummyManager
init(dummyManager: DummyManager) {
self.dummyManager = dummyManager
super.init(nibName: nil, bundle: nil)
}
}
The previous code is fine because it encapsulates the attribute correctly and clearly show dependencies to external APIs.
When working with Storyboards we cannot choose the init method being called (a custom init method is being called).
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let dummyVC = mainStoryboard.instantiateViewControllerWithIdentifier("DummyVC") as! DummyVC
dummyVC.dummyManager = DummyManager() // ERROR: would require dummyManager to have public scope
Is there any way to inject dependencies the same way while keeping attributes private and constants (let) ?
View controllers in storyboards are always initialised using
init?(coder aDecoder: NSCoder)
there's no way around that.
An alternative would be to have…
class DummyVC: UIViewController {
private var dummyManager: DummyManager!
func configure(dummyManager: DummyManager) {
self.dummyManager = dummyManager
}
}
and then…
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let dummyVC = mainStoryboard.instantiateViewControllerWithIdentifier("DummyVC") as! DummyVC
dummyVC.configure(dummyManager: DummyManager())
or
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
switch segue.destination {
case let dummyVC as DummyVC:
dummyVC.configure(dummyManager: DummyManager())
default:
break
}
}
Whilst not perfect (using let rather than var) the property being private and an implicitly unwrapped optional means it must be set (or the app will crash on use), and that can only happen from within the containing class.
I've adopted this throughout my apps, and find it quite a nice way to ensure all properties are set. Just remember to update the configure func when a property is added to a class.

Pageviewcontroller can't instantiate controller from XIB

I have a pageViewController nib with its class pageViewControllerClass. On it I instantiate a myCustomViewController nib with its myCustomViewControllerClass that has outlets.
I use
guard let myViewController1 = UINib(nibName: "myCustomViewControllerClass", bundle: Bundle(for: myCustomViewControllerClass.self)).instantiate(withOwner: self, options: nil).first as? myCustomViewControllerClass else { return }
I add it to view controller using:
setViewControllers([myViewController1], direction: .forward, animated: true, completion: nil)
While there are not outlets connected from myCustomViewController to myCustomViewController class the nib loads fine, but when an outlet is connected my app crashes saying
pageViewController this class is not key value coding-compliant for
the key myOutletName.
I've tried using bundle as nil, setting instantiate(owner:myCustomClass.self), setting bundle as main. So far it keeps crashing. I would greatly appreciate suggestions on what I'm doing wrong.
I have found the solution, I used
let myViewController1 = myCustomViewControllerClass()
and just passed it to the
setViewControllers([myViewController1], direction: .forward, animated: true, completion: nil)
It was a matter of overthinking the solution.

Presenting a a ViewController on a xib file in SWIFT

I have a xib file which I have created a pop up controller on a UIView. This pop up controller animates up when a button is pressed on a View Controller 1. I then have a button on the UIView which when pressed I want to present another View Controller (View Controller 2). code looks like:
class PopUpViewControllerSwift : UIViewController {
#IBAction func goToVC2(sender: UIButton) {
self.removeAnimate()
let VC2: VC2 = self.storyboard?.instantiateViewControllerWithIdentifier("VC2") as VC2
var modalStyle: UIModalTransitionStyle = UIModalTransitionStyle.CoverVertical
VC2.modalTransitionStyle = modalStyle
presentViewController(VC2, animated: true, completion: nil)
}
}
Although thou when the button is pressed it crashes, no error or callbacks or anything. Obviously this would normally work if it was just a regular View Controller but because I am doing it inside a pop Up View which has been animated on top of another View I think that is the problem?
Can anybody help?
thanks
I did this in a UICollectionView didSelectItemAtIndexPath method.
// Register Nib
let newViewController = NewViewController(nibName: "NewViewController", bundle: nil)
// Present View "Modally"
self.present(newViewController, animated: true, completion: nil)
I set the ID of the ViewController to the same as the file name so I would be sure to reference the right ViewController.
Make sure self.storyboard is not nil.
Also confirm that VC2 is one of the ViewController's identity in the storyboard file.
I hope it could help you.
if VC2 is in a storyboard file, try to use following command to get VC2
let storyboard = UIStoryboard(name: "YourStoryboardName(maybe 'Main')", bundle: nil)
let vc2 = storyboard.instantiateViewControllerWithIdentifier("VC2") as VC2
or if VC2 is in a nib file, try to use following command to get VC2
let vc2 = VC2(nibName: "YourNibName", bundle: nil)
It should be like this:
let vc = MyViewController(nibName: "MyViewController", bundle: nil)
present(vc, animated: true, completion: nil)