Passing a Dependency to Tab Bar View Controllers - swift

I'm trying to refactor an app to use dependency injection for the core data stack.
There's a great explanation here:
http://cleanswifter.com/dependency-injection-with-storyboards/
My problem is that in my app the view controllers linked by the uittabbarcontroller are embedded in navigation controllers, so this code:
if let tab = window?.rootViewController as? UITabBarController {
for child in tab.viewControllers ?? [] {
if let top = child as? PersistenceStackClient {
top.set(stack: persistenceStack)
}
}
}
doesn't ever see them.
My instinct is that I need to change the for child in to something that references the child of the navigationcontroller, but all attempts have failed.
I'm thinking I need something along the lines of:
for child in tab.navigationController?.viewControllers
but that doesn't seem to do anything.

I think I have solved this:
if let tab = window?.rootViewController as? UITabBarController {
for child in tab.viewControllers ?? [] {
for nav in child.childViewControllers {
if let top = nav as? PersistenceStackClient {
top.setStack(stack: coreDataStack)
}
}
}
}

Related

how do i bring a viewcontroller from the navigation controller to the front

Can i bring a specific ViewController from my Navigation Controller's stack to the front without reloading it or closing any of the others first? Pretty much like popToViewController but without removing the top ones
Generally remove from viewControllers and push it again
let nav = UINavigationController(rootViewController: UIViewController())
if nav.viewControllers.count > 1 {
let last = nav.viewControllers.removeLast()
nav.pushViewController(last, animated: true)
}
You can use the self.navigationController?.viewControllers to get all the navigation stack controllers and change the position as you want.
Here in the example, First I find the Controller from the navigation stack and move at last.
if let navigationController = self.navigationController,
let indexForController = navigationController.viewControllers.firstIndex(where: {$0 is YourControllerName}) {
let removedController = self.navigationController?.viewControllers.remove(at: indexForController)
self.navigationController?.viewControllers.append(removedController!)
}

How to check if UIViewController is already being displayed?

I'm working on an app that displays a today extension with some information. When I tap on the today extension, it opens the app and navigates to a subview from the root to display the information. Normally the user would then click the back arrow to go back to the main view, but there is no way to tell if this is actually done. It is possible for the user to go back to the today extension and tap again. When this is done, the subview is opened once again with new information. If this is done a bunch of times, I end up with a bunch of instances of the subview and I have to click the back button on each of them to get back to the main view.
My question: Is it possible to check if the subview is already visible? I'd like to be able to just send updated information to it, instead of having to display an entirely new view.
I am currently handling this by keeping the instance of the UIViewController at the top of my root. If it is not nil, then I just pass the information to it and redraw. If it is nil, then I call performSegue and create a new one.
I just think that there must be a better way of handling this.
Edit: Thanks to the commenter below, I came up with this code that seems to do what I need.
if let quoteView = self.navigationController?.topViewController as? ShowQuoteVC {
quoteView.updateQuoteInformation(usingQuote: QuoteService.instance.getQuote(byQuoteNumber: quote))
}
else {
performSegue(withIdentifier: "showQuote", sender: quote)
}
This is different from the suggested post where the answer is:
if (self.navigationController.topViewController == self) {
//the view is currently displayed
}
In this case, it didn't work because I when I come in to the app from the Today Extension, it goes to the root view controller. I needed to check whether a subview is being displayed, and self.navigationController.topViewcontroller == self will never work because I am not checking to see if the top view controller is the root view controller. The suggestions in this post are more applicable to what I am trying to accomplish.
u can use this extension to check for currently displayed through the UIApplication UIViewController:
extension UIApplication {
class func topViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(base: nav.visibleViewController)
}
if let tab = base as? UITabBarController {
if let selected = tab.selectedViewController {
return topViewController(base: selected)
}
}
if let presented = base?.presentedViewController {
return topViewController(base: presented)
}
return base
}
}
and usage example:
if let topController = UIApplication.topViewController() {
if !topController.isKind(of: MainViewController.self) { //MainViewController- the controller u wish to equal its type
// do action...
}
}

How to get the background color of the view from inside an Embedded View

And Thanks in advance for any help.
I have a an embedded view that I want to load in multiple ViewControllers. this is working fine. The problem comes when I try to get the background color of the main View.
Image is VC setup
So I have tried all sorts. I thought about using userDefaults and save the color when the main VC loads and get the colour from userDefaults in the embedded VC, but this feels like a bit of a hack.
The Code below obviously will only get the colour of the view in the embedded VC. Im thinking there must be away to check superview or something?
if self.view.backgroundColor!.isEqual(UIColor.White) {
print("Background is White")
} else {
print("not White")
}
I hope this makes sense. Again Thanks in advance for any enlightenment.
You can try this if it's the root
if let roo = (UIApplication.shared.delegate as! AppDelegate).window?.rootViewController {
print(roo.view.backgroundColor)
if roo.view.backgroundColor == .white {
print("Yes")
}
else {
print("No")
}
}
if not You can also create a delegate like
class ChildVC: UIViewController {
weak var delegate:ParentVC?
}
and in viewDidLoad of parent
if let fir = self.children.first as? ChildVC {
fir.delegate = self
}
After that you can get the parent color with
delegate?.view.backgroundColor

Navigation controller to view controller that is not initial view controller

Is it possible to add navigation contoller and tab bar to view controller that is not initial view controller?
My “Initial view controller” is Login screen. There’s no need for navigation controller and tab bar.
Navigation controller and tab bar wont appear when i just add them from “Editor -> Embed in -> … “
When Login is successful, then I use this code:
if let viewController = self.storyboard?.instantiateViewController(withIdentifier: "mainView") {
UIApplication.shared.keyWindow?.rootViewController = viewController
self.dismiss(animated: true, completion: nil)
}
I use Xcode 9 and swift 4.
Thank you!
I think you may be looking at this wrong. Always having the login view as the root controller in the view hierarchy, even when not needed would be bad practice. And the LoginController should not own a MainController, but the MainController should own a LoginController. You should make your main view (with the nav controller/tab controller) your root view, and in the viewWillAppear method simply check if the user is authenticated; push the login controller modally if the user is not authenticated. That way your view hierarchy is a bit less complex.
You could have a provider class CurrentUser
class CurrentUser {
var user: User? {
guard let userData = UserDefaults.standard.object(forKey: "user") as? Data,
let user = NSKeyedUnarchiver.unarchiveObject(with: userData) as? User else {
return nil
}
return user
}
static let shared = CurrentUser()
}
And in MainController.viewWillAppear
override func viewWillAppear() {
super.viewWillAppear()
guard let user = CurrentUser.shared.user else {
pushViewController(LoginController(), animated: true)
}
// do additional setup
}
Just my $0.02.
Just to answer your question, not only is it possible but it is common. You can put a navigation controller wherever you want. All that the navigation controller does is create a starting point for the progression of view controllers that are to follow by keeping that history of view controllers alive and in order. Therefore, if you had an app with 3 distinct sections, you may most likely want to put a navigation controller in each section so that the user would be able to drill down into each section independently.

Swift - Accessing child label text from parent after addSubview

Hi have an app that adds multiple subviews (UIViews) to the parent using:
if let page:UIViewController = self.storyboard?.instantiateViewControllerWithIdentifier("recentVC") as? RecentViewController {
page.view.frame = self.frame
scrollView.addSubview(page.view)
}
In RecentViewController I have a number of elements (labels, image views etc). I would like to set these from the parent view controller. Is this possible and if so any resources?
I have been searching for a while but I can only find info when segues are used.
Thanks
Since you are using storyboard you can create referencing outlets for views in RecentViewController by dragging them from storyboard to view controller class. Then you can access these properties directly as follow :
if let page = self.storyboard?.instantiateViewControllerWithIdentifier("recentVC") as? RecentViewController {
page.view.frame = self.frame
page.someChild.image = someImage // raw example
self.recentViewController = page
scrollView.addSubview(page.view)
}
You have most the work done for you, create a property in the parent view controller to keep the child view controller.
if let page = self.storyboard?.instantiateViewControllerWithIdentifier("recentVC") as? RecentViewController {
page.view.frame = self.frame
self.recentViewController = page
scrollView.addSubview(page.view)
}
Now you can access anything in the child view controller with self.recentViewController.<childControl>.
IMPORTANT NOTE: When setting up this kind of parent-child relationships, you should use the method from Creating Custom Container View Controllers
if let page = self.storyboard?.instantiateViewControllerWithIdentifier("recentVC") as? RecentViewController {
self.recentViewController = page
self.addChildViewController(page)
page.view.frame = self.frame
scrollView.addSubview(page.view)
page.didMoveToParentViewController(self)
}
I know this looks mysterious and unnecessary but it will prevent odd nearly untraceable issues in the future.
Oh, and if you ever need to remove the child view
self.recentViewController.willMoveToParentViewController(nil)
self.recentViewController.view.removeFromSuperview()
self.recentViewController.removeFromParentViewController()